|  | /* | 
|  | 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 mock | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "compress/gzip" | 
|  | "crypto/x509" | 
|  | "encoding/json" | 
|  | "encoding/pem" | 
|  | "fmt" | 
|  | "io/ioutil" | 
|  | "net/http" | 
|  | "os" | 
|  | "testing" | 
|  | "time" | 
|  |  | 
|  | "io" | 
|  |  | 
|  | "github.com/SermoDigital/jose/jws" | 
|  | "github.com/apid/istioApigeeAdapter/common" | 
|  | ) | 
|  |  | 
|  | var testMockServer *MockServer | 
|  |  | 
|  | func TestMain(m *testing.M) { | 
|  | var err error | 
|  | testMockServer, err = StartMockServer(0) | 
|  | if err != nil { | 
|  | panic(err.Error()) | 
|  | } | 
|  |  | 
|  | result := m.Run() | 
|  | testMockServer.Stop() | 
|  | os.Exit(result) | 
|  | } | 
|  |  | 
|  | func TestServerSanity(t *testing.T) { | 
|  | if mockKeyPEM == nil { | 
|  | t.Fatal("Expected mock key to be generated") | 
|  | } | 
|  | if mockCertPEM == nil { | 
|  | t.Fatal("Expected mock cert to be generated") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPublicKey(t *testing.T) { | 
|  | resp, err := http.Get(fmt.Sprintf("http://%s/publicKey", testMockServer.Address())) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got HTTP status code %d", resp.StatusCode) | 
|  | } | 
|  |  | 
|  | body, err := ioutil.ReadAll(resp.Body) | 
|  | if err != nil { | 
|  | t.Fatalf("Error reading body: %s", err) | 
|  | } | 
|  | pb, _ := pem.Decode(body) | 
|  | if pb == nil { | 
|  | t.Fatalf("Failed decoding public key body \"%s\"", string(body)) | 
|  | } | 
|  | if pb.Type != "CERTIFICATE" { | 
|  | t.Fatalf("Got back invalid PEM type %s", pb.Type) | 
|  | } | 
|  |  | 
|  | cert, err := x509.ParseCertificate(pb.Bytes) | 
|  | if err != nil { | 
|  | t.Fatalf("Error decoding certificate: %s", err) | 
|  | } | 
|  | if cert.Subject.CommonName != "mockserver" { | 
|  | t.Fatalf("Common name does not match \"mockserver\": \"%s\"", cert.Subject.CommonName) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestProducts(t *testing.T) { | 
|  | resp, err := http.Get(fmt.Sprintf("http://%s/products", testMockServer.Address())) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got HTTP status code %d", resp.StatusCode) | 
|  | } | 
|  | if resp.Header.Get("content-type") != "application/json" { | 
|  | t.Fatalf("Invalid content typs %s", resp.Header.Get("content-type")) | 
|  | } | 
|  |  | 
|  | dec := json.NewDecoder(resp.Body) | 
|  | var products common.APIProductResponse | 
|  | err = dec.Decode(&products) | 
|  | if err != nil { | 
|  | t.Fatalf("Error decoding response json: %s", err) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAPIKeyWrongFormat(t *testing.T) { | 
|  | req := common.VerifyAPIKeyRequest{} | 
|  | requestBod, _ := json.Marshal(&req) | 
|  | resp, err := http.DefaultClient.Post( | 
|  | fmt.Sprintf("http://%s/verifyApiKey", testMockServer.Address()), | 
|  | "text/plain", | 
|  | bytes.NewBuffer(requestBod)) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 404 { | 
|  | t.Fatalf("Got HTTP status code %d -- expected 404", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAPIKeyNotJSON(t *testing.T) { | 
|  | resp, err := http.DefaultClient.Post( | 
|  | fmt.Sprintf("http://%s/verifyApiKey", testMockServer.Address()), | 
|  | "application/json", | 
|  | bytes.NewBuffer([]byte("Hello!"))) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 500 { | 
|  | t.Fatalf("Got HTTP status code %d -- expected 500", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAPIKeyNoKey(t *testing.T) { | 
|  | req := common.VerifyAPIKeyRequest{} | 
|  | requestBod, _ := json.Marshal(&req) | 
|  | resp, err := http.DefaultClient.Post( | 
|  | fmt.Sprintf("http://%s/verifyApiKey", testMockServer.Address()), | 
|  | "application/json", | 
|  | bytes.NewBuffer(requestBod)) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 401 { | 
|  | t.Fatalf("Got HTTP status code %d -- expected 401", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAPIKeyBadKey(t *testing.T) { | 
|  | req := common.VerifyAPIKeyRequest{ | 
|  | Key: "foobar", | 
|  | } | 
|  | requestBod, _ := json.Marshal(&req) | 
|  | resp, err := http.DefaultClient.Post( | 
|  | fmt.Sprintf("http://%s/verifyApiKey", testMockServer.Address()), | 
|  | "application/json", | 
|  | bytes.NewBuffer(requestBod)) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 401 { | 
|  | t.Fatalf("Got HTTP status code %d -- expected 401", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAPIKeySuccess(t *testing.T) { | 
|  | req := common.VerifyAPIKeyRequest{ | 
|  | Key: ValidAPIKey1, | 
|  | } | 
|  | requestBod, _ := json.Marshal(&req) | 
|  | resp, err := http.DefaultClient.Post( | 
|  | fmt.Sprintf("http://%s/verifyApiKey", testMockServer.Address()), | 
|  | "application/json", | 
|  | bytes.NewBuffer(requestBod)) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got HTTP status code %d -- expected 200", resp.StatusCode) | 
|  | } | 
|  |  | 
|  | var respBody common.VerifyAPIKeyResponse | 
|  | dec := json.NewDecoder(resp.Body) | 
|  | err = dec.Decode(&respBody) | 
|  | if err != nil { | 
|  | t.Fatalf("Error reading JSON response: %s", err) | 
|  | } | 
|  |  | 
|  | _, err = jws.ParseJWT([]byte(respBody.Token)) | 
|  | if err != nil { | 
|  | t.Fatalf("Error parsing JWT: %s", err) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishBadJSON(t *testing.T) { | 
|  | resp, err := sendAnalytics([]byte("NOTJSON"), "application/json", | 
|  | ValidPublishKey, ValidPublishSecret, false) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 500 { | 
|  | t.Fatalf("Got status code %d and expected 500", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishNotJSON(t *testing.T) { | 
|  | resp, err := sendAnalytics([]byte("{}"), "text/plain", | 
|  | ValidPublishKey, ValidPublishSecret, false) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got status code %d and expected 200", resp.StatusCode) | 
|  | } | 
|  |  | 
|  | var respBody common.AnalyticsResponse | 
|  | dec := json.NewDecoder(resp.Body) | 
|  | err = dec.Decode(&respBody) | 
|  | if err != nil { | 
|  | t.Fatalf("Error reading JSON response: %s", err) | 
|  | } | 
|  |  | 
|  | if respBody.Accepted != 0 { | 
|  | t.Fatalf("%d records were accepted", respBody.Accepted) | 
|  | } | 
|  | if respBody.Rejected != 1 { | 
|  | t.Fatalf("%d records were rejected", respBody.Rejected) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishEmpty(t *testing.T) { | 
|  | resp, err := sendAnalytics([]byte("{}"), "application/json", | 
|  | ValidPublishKey, ValidPublishSecret, false) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got status code %d and expected 200", resp.StatusCode) | 
|  | } | 
|  |  | 
|  | var respBody common.AnalyticsResponse | 
|  | dec := json.NewDecoder(resp.Body) | 
|  | err = dec.Decode(&respBody) | 
|  | if err != nil { | 
|  | t.Fatalf("Error reading JSON response: %s", err) | 
|  | } | 
|  |  | 
|  | if respBody.Accepted != 0 { | 
|  | t.Fatalf("%d records were accepted", respBody.Accepted) | 
|  | } | 
|  | if respBody.Rejected != 0 { | 
|  | t.Fatalf("%d records were rejected", respBody.Rejected) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishWrongUser(t *testing.T) { | 
|  | resp, err := sendAnalytics([]byte("{}"), "application/json", | 
|  | "NOTVALID", ValidPublishSecret, false) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 401 { | 
|  | t.Fatalf("Got status code %d and expected 401", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishWrongPW(t *testing.T) { | 
|  | resp, err := sendAnalytics([]byte("{}"), "application/json", | 
|  | ValidPublishKey, "NOPE", false) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 401 { | 
|  | t.Fatalf("Got status code %d and expected 401", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishNoAuth(t *testing.T) { | 
|  | resp, err := http.Post( | 
|  | fmt.Sprintf("http://%s/edgemicro/axpublisher/organization/foo/environment/test", testMockServer.Address()), | 
|  | "application/json", bytes.NewReader([]byte("{}"))) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 401 { | 
|  | t.Fatalf("Got status code %d and expected 401", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAxPublishCompressed(t *testing.T) { | 
|  | requestBod, err := json.Marshal(makeRequest(10)) | 
|  | if err != nil { | 
|  | t.Fatalf("Error making JSON: %s", err) | 
|  | } | 
|  | resp, err := sendAnalytics(requestBod, "application/json", | 
|  | ValidPublishKey, ValidPublishSecret, true) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got status code %d", resp.StatusCode) | 
|  | } | 
|  |  | 
|  | var respBody common.AnalyticsResponse | 
|  | dec := json.NewDecoder(resp.Body) | 
|  | err = dec.Decode(&respBody) | 
|  | if err != nil { | 
|  | t.Fatalf("Error reading JSON response: %s", err) | 
|  | } | 
|  |  | 
|  | if respBody.Accepted != 10 { | 
|  | t.Fatalf("Only %d record accepted", respBody.Accepted) | 
|  | } | 
|  | if respBody.Rejected != 0 { | 
|  | t.Fatalf("%d records were rejected", respBody.Rejected) | 
|  | } | 
|  | } | 
|  |  | 
|  | func sendAnalytics(body []byte, contentType, user, pw string, compress bool) (*http.Response, error) { | 
|  | var bod io.Reader | 
|  |  | 
|  | if compress { | 
|  | bb := &bytes.Buffer{} | 
|  | zw := gzip.NewWriter(bb) | 
|  | zw.Write(body) | 
|  | zw.Close() | 
|  | bod = bb | 
|  | } else { | 
|  | bod = bytes.NewBuffer(body) | 
|  | } | 
|  |  | 
|  | req, err := http.NewRequest( | 
|  | "POST", | 
|  | fmt.Sprintf("http://%s/edgemicro/axpublisher/organization/foo/environment/test", testMockServer.Address()), | 
|  | bod) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | req.Header.Set("content-type", contentType) | 
|  | if compress { | 
|  | req.Header.Set("content-encoding", "gzip") | 
|  | } | 
|  | req.SetBasicAuth(user, pw) | 
|  | return http.DefaultClient.Do(req) | 
|  | } | 
|  |  | 
|  | func TestRegionCheck(t *testing.T) { | 
|  | req, err := http.NewRequest( | 
|  | "GET", | 
|  | fmt.Sprintf("http://%s/edgemicro/region/organization/foo/environment/test", testMockServer.Address()), | 
|  | nil) | 
|  | if err != nil { | 
|  | t.Fatal("Can't make request") | 
|  | } | 
|  | req.SetBasicAuth(ValidPublishKey, ValidPublishSecret) | 
|  | resp, err := http.DefaultClient.Do(req) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 200 { | 
|  | t.Fatalf("Got status code %d", resp.StatusCode) | 
|  | } | 
|  |  | 
|  | var rr common.RegionResponse | 
|  | dec := json.NewDecoder(resp.Body) | 
|  | err = dec.Decode(&rr) | 
|  | if err != nil { | 
|  | t.Fatalf("Error reading JSON response: %s", err) | 
|  | } | 
|  |  | 
|  | if rr.Host != testMockServer.Address() { | 
|  | t.Fatalf("Invalid host response %s", rr.Host) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestRegionCheckNoAuth(t *testing.T) { | 
|  | req, err := http.NewRequest( | 
|  | "GET", | 
|  | fmt.Sprintf("http://%s/edgemicro/region/organization/foo/environment/test", testMockServer.Address()), | 
|  | nil) | 
|  | if err != nil { | 
|  | t.Fatal("Can't make request") | 
|  | } | 
|  | resp, err := http.DefaultClient.Do(req) | 
|  | if err != nil { | 
|  | t.Fatalf("Network error sending request: %s", err) | 
|  | } | 
|  | defer resp.Body.Close() | 
|  | if resp.StatusCode != 401 { | 
|  | t.Fatalf("Got status code %d", resp.StatusCode) | 
|  | } | 
|  | } | 
|  |  | 
|  | func makeRequest(numRecords int) *common.AnalyticsRequest { | 
|  | var recs []common.AnalyticsRecord | 
|  |  | 
|  | for i := 0; i < numRecords; i++ { | 
|  | now := time.Now().UnixNano() / 1000000 | 
|  | nr := common.AnalyticsRecord{ | 
|  | ClientReceivedStartTimestamp: now, | 
|  | ClientReceivedEndTimestamp:   now + 1, | 
|  | ClientSentStartTimestamp:     now + 10, | 
|  | ClientSentEndTimestamp:       now + 11, | 
|  | RecordType:                   "APIAnalytics", | 
|  | APIProxy:                     "helloworld", | 
|  | RequestURI:                   "http://hello/world", | 
|  | RequestPath:                  "/world", | 
|  | RequestVerb:                  "GET", | 
|  | ClientIP:                     "192.168.201.100", | 
|  | UserAgent:                    "Testing", | 
|  | APIProxyRevision:             1, | 
|  | ResponseStatusCode:           200, | 
|  | } | 
|  | recs = append(recs, nr) | 
|  | } | 
|  |  | 
|  | return &common.AnalyticsRequest{ | 
|  | Records: recs, | 
|  | } | 
|  | } |