From c7a4af31709526bb7911f7d7618c7770ce38ab28 Mon Sep 17 00:00:00 2001 From: He Weiwei Date: Fri, 28 May 2021 17:09:48 +0800 Subject: [PATCH] test: mock the API requests for the huawei registry (#15009) Signed-off-by: He Weiwei --- src/common/http/client.go | 5 + src/go.mod | 1 + src/go.sum | 9 +- src/pkg/reg/adapter/huawei/huawei_adapter.go | 17 +- .../reg/adapter/huawei/huawei_adapter_test.go | 42 +- src/pkg/reg/adapter/huawei/image_registry.go | 19 +- .../reg/adapter/huawei/image_registry_test.go | 100 +++-- src/vendor/github.com/h2non/parth/LICENSE | 22 ++ src/vendor/github.com/h2non/parth/README.md | 196 +++++++++ src/vendor/github.com/h2non/parth/go.mod | 1 + src/vendor/github.com/h2non/parth/parth.go | 349 ++++++++++++++++ src/vendor/github.com/h2non/parth/segindex.go | 118 ++++++ src/vendor/github.com/h2non/parth/segtostr.go | 305 ++++++++++++++ src/vendor/gopkg.in/h2non/gock.v1/.gitignore | 30 ++ src/vendor/gopkg.in/h2non/gock.v1/.travis.yml | 29 ++ src/vendor/gopkg.in/h2non/gock.v1/History.md | 128 ++++++ src/vendor/gopkg.in/h2non/gock.v1/LICENSE | 24 ++ src/vendor/gopkg.in/h2non/gock.v1/README.md | 373 ++++++++++++++++++ src/vendor/gopkg.in/h2non/gock.v1/go.mod | 8 + src/vendor/gopkg.in/h2non/gock.v1/gock.go | 178 +++++++++ src/vendor/gopkg.in/h2non/gock.v1/matcher.go | 137 +++++++ src/vendor/gopkg.in/h2non/gock.v1/matchers.go | 261 ++++++++++++ src/vendor/gopkg.in/h2non/gock.v1/mock.go | 146 +++++++ src/vendor/gopkg.in/h2non/gock.v1/options.go | 8 + src/vendor/gopkg.in/h2non/gock.v1/request.go | 325 +++++++++++++++ .../gopkg.in/h2non/gock.v1/responder.go | 87 ++++ src/vendor/gopkg.in/h2non/gock.v1/response.go | 186 +++++++++ src/vendor/gopkg.in/h2non/gock.v1/store.go | 100 +++++ .../gopkg.in/h2non/gock.v1/transport.go | 112 ++++++ src/vendor/gopkg.in/h2non/gock.v1/version.go | 4 + src/vendor/modules.txt | 5 + 31 files changed, 3278 insertions(+), 47 deletions(-) create mode 100644 src/vendor/github.com/h2non/parth/LICENSE create mode 100644 src/vendor/github.com/h2non/parth/README.md create mode 100644 src/vendor/github.com/h2non/parth/go.mod create mode 100644 src/vendor/github.com/h2non/parth/parth.go create mode 100644 src/vendor/github.com/h2non/parth/segindex.go create mode 100644 src/vendor/github.com/h2non/parth/segtostr.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/.gitignore create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/.travis.yml create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/History.md create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/LICENSE create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/README.md create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/go.mod create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/gock.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/matcher.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/matchers.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/mock.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/options.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/request.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/responder.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/response.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/store.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/transport.go create mode 100644 src/vendor/gopkg.in/h2non/gock.v1/version.go diff --git a/src/common/http/client.go b/src/common/http/client.go index 563c18b2f..43740c7b0 100644 --- a/src/common/http/client.go +++ b/src/common/http/client.go @@ -82,6 +82,11 @@ type Client struct { client *http.Client } +// GetClient returns the http.Client +func (c *Client) GetClient() *http.Client { + return c.client +} + // GetHTTPTransport returns HttpTransport based on insecure configuration func GetHTTPTransport(clientType uint) *http.Transport { switch clientType { diff --git a/src/go.mod b/src/go.mod index 575044865..10a486542 100644 --- a/src/go.mod +++ b/src/go.mod @@ -73,6 +73,7 @@ require ( gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect gopkg.in/fatih/pool.v2 v2.0.0 // indirect gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect + gopkg.in/h2non/gock.v1 v1.0.16 gopkg.in/yaml.v2 v2.3.0 helm.sh/helm/v3 v3.4.2 k8s.io/api v0.19.4 diff --git a/src/go.sum b/src/go.sum index 76af6fbb1..dc1cfd126 100644 --- a/src/go.sum +++ b/src/go.sum @@ -535,6 +535,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -736,6 +738,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/ncw/swift v1.0.49 h1:eQaKIjSt/PXLKfYgzg01nevmO+CMXfXGRhB1gOhDs7E= github.com/ncw/swift v1.0.49/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= @@ -992,9 +995,9 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -1076,6 +1079,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1285,6 +1289,9 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU= gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/h2non/gentleman.v1 v1.0.4/go.mod h1:JYuHVdFzS4MKOXe0o+chKJ4hCe6tqKKw9XH9YP6WFrg= +gopkg.in/h2non/gock.v1 v1.0.16 h1:F11k+OafeuFENsjei5t2vMTSTs9L62AdyTe4E1cgdG8= +gopkg.in/h2non/gock.v1 v1.0.16/go.mod h1:XVuDAssexPLwgxCLMvDTWNU5eqklsydR6I5phZ9oPB8= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/src/pkg/reg/adapter/huawei/huawei_adapter.go b/src/pkg/reg/adapter/huawei/huawei_adapter.go index d4f1545c0..0311326c3 100644 --- a/src/pkg/reg/adapter/huawei/huawei_adapter.go +++ b/src/pkg/reg/adapter/huawei/huawei_adapter.go @@ -1,14 +1,29 @@ +// Copyright Project Harbor Authors +// +// 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 huawei import ( "encoding/json" "fmt" - "github.com/goharbor/harbor/src/pkg/registry/auth/basic" "io/ioutil" "net/http" "regexp" "strings" + "github.com/goharbor/harbor/src/pkg/registry/auth/basic" + common_http "github.com/goharbor/harbor/src/common/http" "github.com/goharbor/harbor/src/common/http/modifier" "github.com/goharbor/harbor/src/lib/log" diff --git a/src/pkg/reg/adapter/huawei/huawei_adapter_test.go b/src/pkg/reg/adapter/huawei/huawei_adapter_test.go index 4f29d5b06..141bf0e8a 100644 --- a/src/pkg/reg/adapter/huawei/huawei_adapter_test.go +++ b/src/pkg/reg/adapter/huawei/huawei_adapter_test.go @@ -1,12 +1,27 @@ +// Copyright Project Harbor Authors +// +// 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 huawei import ( "os" - "strings" "testing" adp "github.com/goharbor/harbor/src/pkg/reg/adapter" "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/stretchr/testify/assert" + gock "gopkg.in/h2non/gock.v1" ) var hwAdapter adp.Adapter @@ -29,6 +44,9 @@ func init() { os.Exit(1) } + a := hwAdapter.(*adapter) + gock.InterceptClient(a.client.GetClient()) + gock.InterceptClient(a.oriClient) } func TestAdapter_Info(t *testing.T) { @@ -40,6 +58,15 @@ func TestAdapter_Info(t *testing.T) { } func TestAdapter_PrepareForPush(t *testing.T) { + defer gock.Off() + gock.Observe(gock.DumpRequest) + + mockRequest().Get("/dockyard/v2/namespaces/domain_repo_new"). + Reply(200).BodyString("{}") + + mockRequest().Post("/dockyard/v2/namespaces").BodyString(`{"namespace":"domain_repo_new"}`). + Reply(200) + repository := &model.Repository{ Name: "domain_repo_new", Metadata: make(map[string]interface{}), @@ -50,18 +77,13 @@ func TestAdapter_PrepareForPush(t *testing.T) { } resource.Metadata = metadata err := hwAdapter.PrepareForPush([]*model.Resource{resource}) - if err != nil { - if strings.HasPrefix(err.Error(), "[401]") { - t.Log("huawei ak/sk is not available", err.Error()) - } else { - t.Error(err) - } - } else { - t.Log("success prepare for push") - } + assert.NoError(t, err) } func TestAdapter_HealthCheck(t *testing.T) { + defer gock.Off() + gock.Observe(gock.DumpRequest) + health, err := hwAdapter.HealthCheck() if err != nil { t.Error(err) diff --git a/src/pkg/reg/adapter/huawei/image_registry.go b/src/pkg/reg/adapter/huawei/image_registry.go index cd90fdb1a..3ade300dc 100644 --- a/src/pkg/reg/adapter/huawei/image_registry.go +++ b/src/pkg/reg/adapter/huawei/image_registry.go @@ -1,14 +1,29 @@ +// Copyright Project Harbor Authors +// +// 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 huawei import ( "encoding/json" "fmt" - "github.com/docker/distribution" - "github.com/goharbor/harbor/src/pkg/reg/model" "io/ioutil" "net/http" "strconv" "time" + + "github.com/docker/distribution" + "github.com/goharbor/harbor/src/pkg/reg/model" ) // FetchArtifacts gets resources from Huawei SWR diff --git a/src/pkg/reg/adapter/huawei/image_registry_test.go b/src/pkg/reg/adapter/huawei/image_registry_test.go index f46cda890..12c08f77f 100644 --- a/src/pkg/reg/adapter/huawei/image_registry_test.go +++ b/src/pkg/reg/adapter/huawei/image_registry_test.go @@ -1,11 +1,28 @@ +// Copyright Project Harbor Authors +// +// 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 huawei import ( + "fmt" "os" - "strings" "testing" + "github.com/docker/distribution" "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/stretchr/testify/assert" + gock "gopkg.in/h2non/gock.v1" ) var HWAdapter adapter @@ -26,47 +43,64 @@ func init() { os.Exit(1) } HWAdapter = *adp.(*adapter) + + gock.InterceptClient(HWAdapter.client.GetClient()) + gock.InterceptClient(HWAdapter.oriClient) +} + +func mockRequest() *gock.Request { + return gock.New("https://swr.cn-north-1.myhuaweicloud.com") +} + +func mockGetJwtToken(repository string) { + mockRequest().Get("/swr/auth/v2/registry/auth"). + MatchParam("scope", fmt.Sprintf("repository:%s:push,pull", repository)). + Reply(200). + JSON(jwtToken{ + Token: "token", + }) } func TestAdapter_FetchArtifacts(t *testing.T) { + defer gock.Off() + gock.Observe(gock.DumpRequest) + + mockRequest().Get("/dockyard/v2/repositories").MatchParam("filter", "center::self"). + BasicAuth("cn-north-1@IJYZLFBKBFN8LOUITAH", "f31e8e2b948265afdae32e83722a7705fd43e154585ff69e64108247750e5d"). + Reply(200). + JSON([]hwRepoQueryResult{ + {Name: "name1"}, + {Name: "name2"}, + }) + resources, err := HWAdapter.FetchArtifacts(nil) - if err != nil { - if strings.HasPrefix(err.Error(), "[401]") { - t.Log("huawei ak/sk is not available", err.Error()) - } else { - t.Error(err) - } - } else { - for _, resource := range resources { - t.Log(*resource) - } - } + assert.NoError(t, err) + assert.Len(t, resources, 2) } func TestAdapter_ManifestExist(t *testing.T) { - exist, desc, err := HWAdapter.ManifestExist("", "") - if err != nil { - if strings.HasPrefix(err.Error(), "[401]") { - t.Log("huawei ak/sk is not available", err.Error()) - } else { - t.Error(err) - } - } else { - if exist { - t.Log(desc.Digest) - } - } + defer gock.Off() + gock.Observe(gock.DumpRequest) + + mockGetJwtToken("sundaymango_mango/hello-world") + mockRequest().Get("/v2/sundaymango_mango/hello-world/manifests/latest"). + Reply(200). + JSON(hwManifest{ + MediaType: distribution.ManifestMediaTypes()[0], + }) + + exist, _, err := HWAdapter.ManifestExist("sundaymango_mango/hello-world", "latest") + assert.NoError(t, err) + assert.True(t, exist) } func TestAdapter_DeleteManifest(t *testing.T) { + defer gock.Off() + gock.Observe(gock.DumpRequest) + + mockGetJwtToken("sundaymango_mango/hello-world") + mockRequest().Delete("/v2/sundaymango_mango/hello-world/manifests/latest").Reply(200) + err := HWAdapter.DeleteManifest("sundaymango_mango/hello-world", "latest") - if err != nil { - if strings.HasPrefix(err.Error(), "[401]") { - t.Log("huawei ak/sk is not available", err.Error()) - } else { - t.Error(err) - } - } else { - t.Error("the manifest is deleted") - } + assert.NoError(t, err) } diff --git a/src/vendor/github.com/h2non/parth/LICENSE b/src/vendor/github.com/h2non/parth/LICENSE new file mode 100644 index 000000000..a5325703a --- /dev/null +++ b/src/vendor/github.com/h2non/parth/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2018 codemodus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/vendor/github.com/h2non/parth/README.md b/src/vendor/github.com/h2non/parth/README.md new file mode 100644 index 000000000..f8dd8df8e --- /dev/null +++ b/src/vendor/github.com/h2non/parth/README.md @@ -0,0 +1,196 @@ +# parth + + go get -u github.com/h2non/parth + +Package parth provides path parsing for segment unmarshaling and slicing. In +other words, parth provides simple and flexible access to (URL) path parameters. + +Along with string, all basic non-alias types are supported. An interface is +available for implementation by user-defined types. When handling an int, uint, +or float of any size, the first valid value within the specified segment will be +used. + +## Usage + +```go +Variables +func Segment(path string, i int, v interface{}) error +func Sequent(path, key string, v interface{}) error +func Span(path string, i, j int) (string, error) +func SubSeg(path, key string, i int, v interface{}) error +func SubSpan(path, key string, i, j int) (string, error) +type Parth + func New(path string) *Parth + func NewBySpan(path string, i, j int) *Parth + func NewBySubSpan(path, key string, i, j int) *Parth + func (p *Parth) Err() error + func (p *Parth) Segment(i int, v interface{}) + func (p *Parth) Sequent(key string, v interface{}) + func (p *Parth) Span(i, j int) string + func (p *Parth) SubSeg(key string, i int, v interface{}) + func (p *Parth) SubSpan(key string, i, j int) string +type Unmarshaler +``` + +### Setup ("By Index") + +```go +import ( + "fmt" + + "github.com/codemodus/parth" +) + +func handler(w http.ResponseWriter, r *http.Request) { + var s string + if err := parth.Segment(r.URL.Path, 4, &s); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + fmt.Println(r.URL.Path) + fmt.Printf("%v (%T)\n", s, s) + + // Output: + // /some/path/things/42/others/3 + // others (string) +} +``` + +### Setup ("By Key") + +```go +import ( + "fmt" + + "github.com/codemodus/parth" +) + +func handler(w http.ResponseWriter, r *http.Request) { + var i int64 + if err := parth.Sequent(r.URL.Path, "things", &i); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + fmt.Println(r.URL.Path) + fmt.Printf("%v (%T)\n", i, i) + + // Output: + // /some/path/things/42/others/3 + // 42 (int64) +} +``` + +### Setup (Parth Type) + +```go +import ( + "fmt" + + "github.com/codemodus/parth" +) + +func handler(w http.ResponseWriter, r *http.Request) { + var s string + var f float32 + + p := parth.New(r.URL.Path) + p.Segment(2, &s) + p.SubSeg("key", 1, &f) + if err := p.Err(); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + fmt.Println(r.URL.Path) + fmt.Printf("%v (%T)\n", s, s) + fmt.Printf("%v (%T)\n", f, f) + + // Output: + // /zero/one/two/key/four/5.5/six + // two (string) + // 5.5 (float32) +} +``` + +### Setup (Unmarshaler) + +```go +import ( + "fmt" + + "github.com/codemodus/parth" +) + +func handler(w http.ResponseWriter, r *http.Request) { + /* + type mytype []byte + + func (m *mytype) UnmarshalSegment(seg string) error { + *m = []byte(seg) + } + */ + + var m mytype + if err := parth.Segment(r.URL.Path, 4, &m); err != nil { + fmt.Fprintln(os.Stderr, err) + } + + fmt.Println(r.URL.Path) + fmt.Printf("%v == %q (%T)\n", m, m, m) + + // Output: + // /zero/one/two/key/four/5.5/six + // [102 111 117 114] == "four" (mypkg.mytype) +} +``` + +## More Info + +### Keep Using http.HandlerFunc And Minimize context.Context Usage + +The most obvious use case for parth is when working with any URL path such as +the one found at http.Request.URL.Path. parth is fast enough that it can be used +multiple times in place of a single use of similar router-parameter schemes or +even context.Context. There is no need to use an alternate http handler function +definition in order to pass data that is already being passed. The http.Request +type already holds URL data and parth is great at handling it. Additionally, +parth takes care of parsing selected path segments into the types actually +needed. Parth not only does more, it's usually faster and less intrusive than +the alternatives. + +### Indexes + +If an index is negative, the negative count begins with the last segment. +Providing a 0 for the second index is a special case which acts as an alias for +the end of the path. An error is returned if: 1. Any index is out of range of +the path; 2. When there are two indexes, the first index does not precede the +second index. + +### Keys + +If a key is involved, functions will only handle the portion of the path +subsequent to the provided key. An error is returned if the key cannot be found +in the path. + +### First Whole, First Decimal (Restated - Important!) + +When handling an int, uint, or float of any size, the first valid value within +the specified segment will be used. + +## Documentation + +View the [GoDoc](http://godoc.org/github.com/codemodus/parth) + +## Benchmarks + + Go 1.11 + benchmark iter time/iter bytes alloc allocs + --------- ---- --------- ----------- ------ + BenchmarkSegmentString-8 30000000 39.60 ns/op 0 B/op 0 allocs/op + BenchmarkSegmentInt-8 20000000 65.60 ns/op 0 B/op 0 allocs/op + BenchmarkSegmentIntNegIndex-8 20000000 86.60 ns/op 0 B/op 0 allocs/op + BenchmarkSpan-8 100000000 18.20 ns/op 0 B/op 0 allocs/op + BenchmarkStdlibSegmentString-8 5000000 454.00 ns/op 50 B/op 2 allocs/op + BenchmarkStdlibSegmentInt-8 3000000 526.00 ns/op 50 B/op 2 allocs/op + BenchmarkStdlibSpan-8 3000000 518.00 ns/op 69 B/op 2 allocs/op + BenchmarkContextLookupSetGet-8 1000000 1984.00 ns/op 480 B/op 6 allocs/op + diff --git a/src/vendor/github.com/h2non/parth/go.mod b/src/vendor/github.com/h2non/parth/go.mod new file mode 100644 index 000000000..c25e66338 --- /dev/null +++ b/src/vendor/github.com/h2non/parth/go.mod @@ -0,0 +1 @@ +module github.com/h2non/parth diff --git a/src/vendor/github.com/h2non/parth/parth.go b/src/vendor/github.com/h2non/parth/parth.go new file mode 100644 index 000000000..3e64d8fee --- /dev/null +++ b/src/vendor/github.com/h2non/parth/parth.go @@ -0,0 +1,349 @@ +// Package parth provides path parsing for segment unmarshaling and slicing. In +// other words, parth provides simple and flexible access to (URL) path +// parameters. +// +// Along with string, all basic non-alias types are supported. An interface is +// available for implementation by user-defined types. When handling an int, +// uint, or float of any size, the first valid value within the specified +// segment will be used. +package parth + +import ( + "errors" +) + +// Unmarshaler is the interface implemented by types that can unmarshal a path +// segment representation of themselves. It is safe to assume that the segment +// data will not include slashes. +type Unmarshaler interface { + UnmarshalSegment(string) error +} + +// Err{Name} values facilitate error identification. +var ( + ErrUnknownType = errors.New("unknown type provided") + + ErrFirstSegNotFound = errors.New("first segment not found by index") + ErrLastSegNotFound = errors.New("last segment not found by index") + ErrSegOrderReversed = errors.New("first segment must precede last segment") + ErrKeySegNotFound = errors.New("segment not found by key") + + ErrDataUnparsable = errors.New("data cannot be parsed") +) + +// Segment locates the path segment indicated by the index i and unmarshals it +// into the provided type v. If the index is negative, the negative count +// begins with the last segment. An error is returned if: 1. The type is not a +// pointer to an instance of one of the basic non-alias types and does not +// implement the Unmarshaler interface; 2. The index is out of range of the +// path; 3. The located path segment data cannot be parsed as the provided type +// or if an error is returned when using a provided Unmarshaler implementation. +func Segment(path string, i int, v interface{}) error { //nolint + var err error + + switch v := v.(type) { + case *bool: + *v, err = segmentToBool(path, i) + + case *float32: + var f float64 + f, err = segmentToFloatN(path, i, 32) + *v = float32(f) + + case *float64: + *v, err = segmentToFloatN(path, i, 64) + + case *int: + var n int64 + n, err = segmentToIntN(path, i, 0) + *v = int(n) + + case *int16: + var n int64 + n, err = segmentToIntN(path, i, 16) + *v = int16(n) + + case *int32: + var n int64 + n, err = segmentToIntN(path, i, 32) + *v = int32(n) + + case *int64: + *v, err = segmentToIntN(path, i, 64) + + case *int8: + var n int64 + n, err = segmentToIntN(path, i, 8) + *v = int8(n) + + case *string: + *v, err = segmentToString(path, i) + + case *uint: + var n uint64 + n, err = segmentToUintN(path, i, 0) + *v = uint(n) + + case *uint16: + var n uint64 + n, err = segmentToUintN(path, i, 16) + *v = uint16(n) + + case *uint32: + var n uint64 + n, err = segmentToUintN(path, i, 32) + *v = uint32(n) + + case *uint64: + *v, err = segmentToUintN(path, i, 64) + + case *uint8: + var n uint64 + n, err = segmentToUintN(path, i, 8) + *v = uint8(n) + + case Unmarshaler: + var s string + s, err = segmentToString(path, i) + if err == nil { + err = v.UnmarshalSegment(s) + } + + default: + err = ErrUnknownType + } + + return err +} + +// Sequent is similar to Segment, but uses a key to locate a segment and then +// unmarshal the subsequent segment. It is a simple wrapper over SubSeg with an +// index of 0. +func Sequent(path, key string, v interface{}) error { + return SubSeg(path, key, 0, v) +} + +// Span returns the path segments between two segment indexes i and j including +// the first segment. If an index is negative, the negative count begins with +// the last segment. Providing a 0 for the last index j is a special case which +// acts as an alias for the end of the path. If the first segment does not begin +// with a slash and it is part of the requested span, no slash will be added. An +// error is returned if: 1. Either index is out of range of the path; 2. The +// first index i does not precede the last index j. +func Span(path string, i, j int) (string, error) { + var f, l int + var ok bool + + if i < 0 { + f, ok = segStartIndexFromEnd(path, i) + } else { + f, ok = segStartIndexFromStart(path, i) + } + if !ok { + return "", ErrFirstSegNotFound + } + + if j > 0 { + l, ok = segEndIndexFromStart(path, j) + } else { + l, ok = segEndIndexFromEnd(path, j) + } + if !ok { + return "", ErrLastSegNotFound + } + + if f == l { + return "", nil + } + + if f > l { + return "", ErrSegOrderReversed + } + + return path[f:l], nil +} + +// SubSeg is similar to Segment, but only handles the portion of the path +// subsequent to the provided key. For example, to access the segment +// immediately after a key, an index of 0 should be provided (see Sequent). An +// error is returned if the key cannot be found in the path. +func SubSeg(path, key string, i int, v interface{}) error { //nolint + var err error + + switch v := v.(type) { + case *bool: + *v, err = subSegToBool(path, key, i) + + case *float32: + var f float64 + f, err = subSegToFloatN(path, key, i, 32) + *v = float32(f) + + case *float64: + *v, err = subSegToFloatN(path, key, i, 64) + + case *int: + var n int64 + n, err = subSegToIntN(path, key, i, 0) + *v = int(n) + + case *int16: + var n int64 + n, err = subSegToIntN(path, key, i, 16) + *v = int16(n) + + case *int32: + var n int64 + n, err = subSegToIntN(path, key, i, 32) + *v = int32(n) + + case *int64: + *v, err = subSegToIntN(path, key, i, 64) + + case *int8: + var n int64 + n, err = subSegToIntN(path, key, i, 8) + *v = int8(n) + + case *string: + *v, err = subSegToString(path, key, i) + + case *uint: + var n uint64 + n, err = subSegToUintN(path, key, i, 0) + *v = uint(n) + + case *uint16: + var n uint64 + n, err = subSegToUintN(path, key, i, 16) + *v = uint16(n) + + case *uint32: + var n uint64 + n, err = subSegToUintN(path, key, i, 32) + *v = uint32(n) + + case *uint64: + *v, err = subSegToUintN(path, key, i, 64) + + case *uint8: + var n uint64 + n, err = subSegToUintN(path, key, i, 8) + *v = uint8(n) + + case Unmarshaler: + var s string + s, err = subSegToString(path, key, i) + if err == nil { + err = v.UnmarshalSegment(s) + } + + default: + err = ErrUnknownType + } + + return err +} + +// SubSpan is similar to Span, but only handles the portion of the path +// subsequent to the provided key. An error is returned if the key cannot be +// found in the path. +func SubSpan(path, key string, i, j int) (string, error) { + si, ok := segIndexByKey(path, key) + if !ok { + return "", ErrKeySegNotFound + } + + if i >= 0 { + i++ + } + if j > 0 { + j++ + } + + s, err := Span(path[si:], i, j) + if err != nil { + return "", err + } + + return s, nil +} + +// Parth manages path and error data for processing a single path multiple +// times while error checking only once. Only the first encountered error is +// stored as all subsequent calls to Parth methods that can error are elided. +type Parth struct { + path string + err error +} + +// New constructs a pointer to an instance of Parth around the provided path. +func New(path string) *Parth { + return &Parth{path: path} +} + +// NewBySpan constructs a pointer to an instance of Parth after preprocessing +// the provided path with Span. +func NewBySpan(path string, i, j int) *Parth { + s, err := Span(path, i, j) + return &Parth{s, err} +} + +// NewBySubSpan constructs a pointer to an instance of Parth after +// preprocessing the provided path with SubSpan. +func NewBySubSpan(path, key string, i, j int) *Parth { + s, err := SubSpan(path, key, i, j) + return &Parth{s, err} +} + +// Err returns the first error encountered by the *Parth receiver. +func (p *Parth) Err() error { + return p.err +} + +// Segment operates the same as the package-level function Segment. +func (p *Parth) Segment(i int, v interface{}) { + if p.err != nil { + return + } + + p.err = Segment(p.path, i, v) +} + +// Sequent operates the same as the package-level function Sequent. +func (p *Parth) Sequent(key string, v interface{}) { + p.SubSeg(key, 0, v) +} + +// Span operates the same as the package-level function Span. +func (p *Parth) Span(i, j int) string { + if p.err != nil { + return "" + } + + s, err := Span(p.path, i, j) + p.err = err + + return s +} + +// SubSeg operates the same as the package-level function SubSeg. +func (p *Parth) SubSeg(key string, i int, v interface{}) { + if p.err != nil { + return + } + + p.err = SubSeg(p.path, key, i, v) +} + +// SubSpan operates the same as the package-level function SubSpan. +func (p *Parth) SubSpan(key string, i, j int) string { + if p.err != nil { + return "" + } + + s, err := SubSpan(p.path, key, i, j) + p.err = err + + return s +} diff --git a/src/vendor/github.com/h2non/parth/segindex.go b/src/vendor/github.com/h2non/parth/segindex.go new file mode 100644 index 000000000..77861f99a --- /dev/null +++ b/src/vendor/github.com/h2non/parth/segindex.go @@ -0,0 +1,118 @@ +package parth + +func segStartIndexFromStart(path string, seg int) (int, bool) { + if seg < 0 { + return 0, false + } + + for n, ct := 0, 0; n < len(path); n++ { + if n > 0 && path[n] == '/' { + ct++ + } + + if ct == seg { + return n, true + } + } + + return 0, false +} + +func segStartIndexFromEnd(path string, seg int) (int, bool) { + if seg > -1 { + return 0, false + } + + for n, ct := len(path)-1, 0; n >= 0; n-- { + if path[n] == '/' || n == 0 { + ct-- + } + + if ct == seg { + return n, true + } + } + + return 0, false +} + +func segEndIndexFromStart(path string, seg int) (int, bool) { + if seg < 1 { + return 0, false + } + + for n, ct := 0, 0; n < len(path); n++ { + if path[n] == '/' && n > 0 { + ct++ + } + + if ct == seg { + return n, true + } + + if n+1 == len(path) && ct+1 == seg { + return n + 1, true + } + } + + return 0, false +} + +func segEndIndexFromEnd(path string, seg int) (int, bool) { + if seg > 0 { + return 0, false + } + + if seg == 0 { + return len(path), true + } + + if len(path) == 1 && path[0] == '/' { + return 0, true + } + + for n, ct := len(path)-1, 0; n >= 0; n-- { + if n == 0 || path[n] == '/' { + ct-- + } + + if ct == seg { + return n, true + } + + } + + return 0, false +} + +func segIndexByKey(path, key string) (int, bool) { //nolint + if path == "" || key == "" { + return 0, false + } + + for n := 0; n < len(path); n++ { + si, ok := segStartIndexFromStart(path, n) + if !ok { + return 0, false + } + + if len(path[si:]) == len(key)+1 { + if path[si+1:] == key { + return si, true + } + + return 0, false + } + + tmpEI, ok := segStartIndexFromStart(path[si:], 1) + if !ok { + return 0, false + } + + if path[si+1:tmpEI+si] == key || n == 0 && path[0] != '/' && path[si:tmpEI+si] == key { + return si, true + } + } + + return 0, false +} diff --git a/src/vendor/github.com/h2non/parth/segtostr.go b/src/vendor/github.com/h2non/parth/segtostr.go new file mode 100644 index 000000000..6e16a911b --- /dev/null +++ b/src/vendor/github.com/h2non/parth/segtostr.go @@ -0,0 +1,305 @@ +package parth + +import ( + "strconv" + "unicode" +) + +func segmentToBool(path string, i int) (bool, error) { + s, err := segmentToString(path, i) + if err != nil { + return false, err + } + + v, err := strconv.ParseBool(s) + if err != nil { + return false, ErrDataUnparsable + } + + return v, nil +} + +func segmentToFloatN(path string, i, size int) (float64, error) { + ss, err := segmentToString(path, i) + if err != nil { + return 0.0, err + } + + s, ok := firstFloatFromString(ss) + if !ok { + return 0.0, err + } + + v, err := strconv.ParseFloat(s, size) + if err != nil { + return 0.0, ErrDataUnparsable + } + + return v, nil +} + +func segmentToIntN(path string, i, size int) (int64, error) { + ss, err := segmentToString(path, i) + if err != nil { + return 0, err + } + + s, ok := firstIntFromString(ss) + if !ok { + return 0, ErrDataUnparsable + } + + v, err := strconv.ParseInt(s, 10, size) + if err != nil { + return 0, ErrDataUnparsable + } + + return v, nil +} + +func segmentToString(path string, i int) (string, error) { + j := i + 1 + if i < 0 { + i-- + } + + s, err := Span(path, i, j) + if err != nil { + return "", err + } + + if s[0] == '/' { + s = s[1:] + } + + return s, nil +} + +func segmentToUintN(path string, i, size int) (uint64, error) { + ss, err := segmentToString(path, i) + if err != nil { + return 0, err + } + + s, ok := firstUintFromString(ss) + if !ok { + return 0, ErrDataUnparsable + } + + v, err := strconv.ParseUint(s, 10, size) + if err != nil { + return 0, ErrDataUnparsable + } + + return v, nil +} + +func subSegToBool(path, key string, i int) (bool, error) { + s, err := subSegToString(path, key, i) + if err != nil { + return false, err + } + + v, err := strconv.ParseBool(s) + if err != nil { + return false, ErrDataUnparsable + } + + return v, nil +} + +func subSegToFloatN(path, key string, i, size int) (float64, error) { + ss, err := subSegToString(path, key, i) + if err != nil { + return 0.0, err + } + + s, ok := firstFloatFromString(ss) + if !ok { + return 0.0, ErrDataUnparsable + } + + v, err := strconv.ParseFloat(s, size) + if err != nil { + return 0.0, ErrDataUnparsable + } + + return v, nil +} + +func subSegToIntN(path, key string, i, size int) (int64, error) { + ss, err := subSegToString(path, key, i) + if err != nil { + return 0, err + } + + s, ok := firstIntFromString(ss) + if !ok { + return 0, ErrDataUnparsable + } + + v, err := strconv.ParseInt(s, 10, size) + if err != nil { + return 0, ErrDataUnparsable + } + + return v, nil +} + +func subSegToString(path, key string, i int) (string, error) { + ki, ok := segIndexByKey(path, key) + if !ok { + return "", ErrKeySegNotFound + } + + i++ + + s, err := segmentToString(path[ki:], i) + if err != nil { + return "", err + } + + return s, nil +} + +func subSegToUintN(path, key string, i, size int) (uint64, error) { + ss, err := subSegToString(path, key, i) + if err != nil { + return 0, err + } + + s, ok := firstUintFromString(ss) + if !ok { + return 0, ErrDataUnparsable + } + + v, err := strconv.ParseUint(s, 10, size) + if err != nil { + return 0, ErrDataUnparsable + } + + return v, nil +} + +func firstUintFromString(s string) (string, bool) { + ind, l := 0, 0 + + for n := 0; n < len(s); n++ { + if unicode.IsDigit(rune(s[n])) { + if l == 0 { + ind = n + } + + l++ + } else { + if l == 0 && s[n] == '.' { + if n+1 < len(s) && unicode.IsDigit(rune(s[n+1])) { + return "0", true + } + + break + } + + if l > 0 { + break + } + } + } + + if l == 0 { + return "", false + } + + return s[ind : ind+l], true +} + +func firstIntFromString(s string) (string, bool) { //nolint + ind, l := 0, 0 + + for n := 0; n < len(s); n++ { + if unicode.IsDigit(rune(s[n])) { + if l == 0 { + ind = n + } + + l++ + } else if s[n] == '-' { + if l == 0 { + ind = n + l++ + } else { + break + } + } else { + if l == 0 && s[n] == '.' { + if n+1 < len(s) && unicode.IsDigit(rune(s[n+1])) { + return "0", true + } + + break + } + + if l > 0 { + break + } + } + } + + if l == 0 { + return "", false + } + + return s[ind : ind+l], true +} + +func firstFloatFromString(s string) (string, bool) { //nolint + c, ind, l := 0, 0, 0 + + for n := 0; n < len(s); n++ { + if unicode.IsDigit(rune(s[n])) { + if l == 0 { + ind = n + } + + l++ + } else if s[n] == '-' { + if l == 0 { + ind = n + l++ + } else { + break + } + } else if s[n] == '.' { + if l == 0 { + ind = n + } + + if c > 0 { + break + } + + l++ + c++ + } else if s[n] == 'e' && l > 0 && n+1 < len(s) && s[n+1] == '+' { + l++ + } else if s[n] == '+' && l > 0 && s[n-1] == 'e' { + if n+1 < len(s) && unicode.IsDigit(rune(s[n+1])) { + l++ + continue + } + + l-- + break + } else { + if l > 0 { + break + } + } + } + + if l == 0 || s[ind:ind+l] == "." { + return "", false + } + + return s[ind : ind+l], true +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/.gitignore b/src/vendor/gopkg.in/h2non/gock.v1/.gitignore new file mode 100644 index 000000000..173b0298b --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/.gitignore @@ -0,0 +1,30 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so +go.sum + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.idea/ +*.iml +*.out +*.tmp diff --git a/src/vendor/gopkg.in/h2non/gock.v1/.travis.yml b/src/vendor/gopkg.in/h2non/gock.v1/.travis.yml new file mode 100644 index 000000000..3e539117c --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/.travis.yml @@ -0,0 +1,29 @@ +language: go + +go: + # - "1.14" + - "1.13" + # - "1.12" + - "1.11" + - "1.10" + # - "stable" + +before_install: + - go get -u github.com/nbio/st + - go get -u github.com/codemodus/parth + - go get -u gopkg.in/h2non/gentleman.v1 + - go get -u -v github.com/axw/gocov/gocov + - go get -u -v github.com/mattn/goveralls + - go get -v -u golang.org/x/lint/golint + - mkdir -p $GOPATH/src/gopkg.in/h2non/gock.v1 + - cp -r . $GOPATH/src/gopkg.in/h2non/gock.v1 + +script: + - diff -u <(echo -n) <(gofmt -s -d ./) + - diff -u <(echo -n) <(go vet ./...) + - diff -u <(echo -n) <(golint ./...) + - go test -v -race -covermode=atomic -coverprofile=coverage.out + - go test ./_examples/*/ -v -race + +after_success: + - goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/src/vendor/gopkg.in/h2non/gock.v1/History.md b/src/vendor/gopkg.in/h2non/gock.v1/History.md new file mode 100644 index 000000000..7a0507f22 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/History.md @@ -0,0 +1,128 @@ + +## v1.0.16 / 2020-11-23 + + * Fix regexp matching issues in headers (#59) + +## v1.0.15 / 2019-07-03 + + * NewMatcher() will now return objects that completely separate one another. (#55) + * add request Options (#49) + * fix typo: function -> func (#52) + * feat(docs): change note + * feat(docs): add net/http support + * Add Basic Auth (#47) + * Update LICENSE (#46) + +## v1.0.14 / 2019-01-31 + + * feat(version): bump to v1.0.14 + * feat: add go.mod + +## v1.0.13 / 2019-01-30 + + * Add PathParam matcher (#42) + +## v1.0.12 / 2018-11-13 + + * Fix possible data race. (#41) + +## v1.0.11 / 2018-10-29 + + * Do not reset response body (#40) + * refactor(travis): remove unsupported versions for golint based on Go release policy support + * feat(gock): add gock.Observe to support inspection of the outgoing intercepted HTTP traffic (#38) + +## v1.0.10 / 2018-09-09 + + * Support multiple response headers with same name #35 (#36) + +## v1.0.9 / 2018-06-14 + + * fix(url-encoding) add exact match test in MatchPath (#34) + * fix(travis): use string notation for Go versions + +## v1.0.8 / 2018-02-28 + + * chore(LICENSE): update year ;) + * feat(docs): add additional tips and examples + * feat(gock): ignore already intercepted http.Client + +## v1.0.7 / 2017-12-21 + + * Make MatchHost case insensitive. (#31) + * refactor(docs): remove codesponsor :( + * add example when request reply with error (#28) + * feat(docs): add sponsor ad + * Add example networking partially enabled (#23) + +## v1.0.6 / 2017-07-27 + + * fix(#23): mock transport deadlock + +## v1.0.5 / 2017-07-26 + + * feat(#25, #24): use content type only if missing while matching JSON/XML + * feat(#24): add CleanUnmatchedRequests() and OffAll() public functions + * feat(version): bump to v1.0.5 + * fix(store): use proper indent style + * fix(mutex): use different mutex for store + * feat(travis): add Go 1.8 CI support + +## v1.0.4 / 2017-02-14 + + * Update README to include most up to date version (#17) + * Update MatchBody() to compare if key + value pairs of JSON match regardless of order they are in. (#16) + * feat(examples): add new example for unmatch case + * refactor(docs): add pook reference + +## 1.0.3 / 14-11-2016 + +- feat(#13): adds `GetUnmatchedRequests()` and `HasUnmatchedRequests()` API functions. + +## 1.0.2 / 10-11-2016 + +- fix(#11): adds `Compression()` method for output HTTP traffic body compression processing and matching. + +## 1.0.1 / 07-09-2016 + +- fix(#9): missing URL query param matcher. + +## 1.0.0 / 19-04-2016 + +- feat(version): first major version release. + +## 0.1.6 / 19-04-2016 + +- fix(#7): if error configured, RoundTripper should reply with `nil` response. + +## 0.1.5 / 09-04-2016 + +- feat(#5): support `ReplyFunc` for convenience. + +## 0.1.4 / 16-03-2016 + +- feat(api): add `IsDone()` method. +- fix(responder): return mock error if present. +- feat(#4): support define request/response body from file disk. + +## 0.1.3 / 09-03-2016 + +- feat(matcher): add content type matcher helper method supporting aliases. +- feat(interceptor): add function to restore HTTP client transport. +- feat(matcher): add URL scheme matcher function. +- fix(request): ignore base slash path. +- feat(api): add Off() method for easier restore and clean up. +- feat(store): add public API for pending mocks. + +## 0.1.2 / 04-03-2016 + +- fix(matcher): body matchers no used by default. +- feat(matcher): add matcher factories for multiple cases. + +## 0.1.1 / 04-03-2016 + +- fix(params): persist query params accordingly. + +## 0.1.0 / 02-03-2016 + +- First release. diff --git a/src/vendor/gopkg.in/h2non/gock.v1/LICENSE b/src/vendor/gopkg.in/h2non/gock.v1/LICENSE new file mode 100644 index 000000000..1911b1778 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/LICENSE @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) 2016-2019 Tomas Aparicio + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/vendor/gopkg.in/h2non/gock.v1/README.md b/src/vendor/gopkg.in/h2non/gock.v1/README.md new file mode 100644 index 000000000..ab4f0f0dc --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/README.md @@ -0,0 +1,373 @@ +# gock [![Build Status](https://travis-ci.org/h2non/gock.svg?branch=master)](https://travis-ci.org/h2non/gock) [![GitHub release](https://img.shields.io/badge/version-v1.0-orange.svg?style=flat)](https://github.com/h2non/gock/releases) [![GoDoc](https://godoc.org/github.com/h2non/gock?status.svg)](https://godoc.org/github.com/h2non/gock) [![Coverage Status](https://coveralls.io/repos/github/h2non/gock/badge.svg?branch=master)](https://coveralls.io/github/h2non/gock?branch=master) [![Go Report Card](https://img.shields.io/badge/go_report-A+-brightgreen.svg)](https://goreportcard.com/report/github.com/h2non/gock) [![license](https://img.shields.io/badge/license-MIT-blue.svg)]() + +Versatile HTTP mocking made easy in [Go](https://golang.org) that works with any `net/http` based stdlib implementation. + +Heavily inspired by [nock](https://github.com/node-nock/nock). +There is also its Python port, [pook](https://github.com/h2non/pook). + +To get started, take a look to the [examples](#examples). + +## Features + +- Simple, expressive, fluent API. +- Semantic API DSL for declarative HTTP mock declarations. +- Built-in helpers for easy JSON/XML mocking. +- Supports persistent and volatile TTL-limited mocks. +- Full regular expressions capable HTTP request mock matching. +- Designed for both testing and runtime scenarios. +- Match request by method, URL params, headers and bodies. +- Extensible and pluggable HTTP matching rules. +- Ability to switch between mock and real networking modes. +- Ability to filter/map HTTP requests for accurate mock matching. +- Supports map and filters to handle mocks easily. +- Wide compatible HTTP interceptor using `http.RoundTripper` interface. +- Works with any `net/http` compatible client, such as [gentleman](https://github.com/h2non/gentleman). +- Network delay simulation (beta). +- Extensible and hackable API. +- Dependency free. + +## Installation + +```bash +go get -u gopkg.in/h2non/gock.v1 +``` + +## API + +See [godoc reference](https://godoc.org/github.com/h2non/gock) for detailed API documentation. + +## How it mocks + +1. Intercepts any HTTP outgoing request via `http.DefaultTransport` or custom `http.Transport` used by any `http.Client`. +2. Matches outgoing HTTP requests against a pool of defined HTTP mock expectations in FIFO declaration order. +3. If at least one mock matches, it will be used in order to compose the mock HTTP response. +4. If no mock can be matched, it will resolve the request with an error, unless real networking mode is enable, in which case a real HTTP request will be performed. + +## Tips + +#### Testing + +Declare your mocks before you start declaring the concrete test logic: + +```go +func TestFoo(t *testing.T) { + defer gock.Off() // Flush pending mocks after test execution + + gock.New("http://server.com"). + Get("/bar"). + Reply(200). + JSON(map[string]string{"foo": "bar"}) + + // Your test code starts here... +} +``` + +#### Race conditions + +If you're running concurrent code, be aware that your mocks are declared first to avoid unexpected +race conditions while configuring `gock` or intercepting custom HTTP clients. + +`gock` is not fully thread-safe, but sensible parts are. +Any help making `gock` more reliable in this sense is appreciated. + +#### Define complex mocks first + +If you're mocking a bunch of mocks in the same test suite, it's recommended to define the more +concrete mocks first, and then the generic ones. + +This approach usually avoids matching unexpected generic mocks (e.g: specific header, body payload...) instead of the generic ones that performs less complex matches. + +#### Disable `gock` traffic interception once done + +In other to minimize potential side effects within your test code, it's a good practice +disabling `gock` once you are done with your HTTP testing logic. + +A Go idiomatic approach for doing this can be using it in a `defer` statement, such as: + +```go +func TestGock (t *testing.T) { + defer gock.Off() + + // ... my test code goes here +} +``` + +#### Intercept an `http.Client` just once + +You don't need to intercept multiple times the same `http.Client` instance. + +Just call `gock.InterceptClient(client)` once, typically at the beginning of your test scenarios. + +#### Restore an `http.Client` after interception + +**NOTE**: this is not required is you are using `http.DefaultClient` or `http.DefaultTransport`. + +As a good testing pattern, you should call `gock.RestoreClient(client)` after running your test scenario, typically as after clean up hook. + +You can also use a `defer` statement for doing it, as you do with `gock.Off()`, such as: + +```go +func TestGock (t *testing.T) { + defer gock.Off() + defer gock.RestoreClient(client) + + // ... my test code goes here +} +``` + +## Examples + +See [examples](https://github.com/h2non/gock/tree/master/_examples) directory for more featured use cases. + +#### Simple mocking via tests + +```go +package test + +import ( + "github.com/nbio/st" + "gopkg.in/h2non/gock.v1" + "io/ioutil" + "net/http" + "testing" +) + +func TestSimple(t *testing.T) { + defer gock.Off() + + gock.New("http://foo.com"). + Get("/bar"). + Reply(200). + JSON(map[string]string{"foo": "bar"}) + + res, err := http.Get("http://foo.com/bar") + st.Expect(t, err, nil) + st.Expect(t, res.StatusCode, 200) + + body, _ := ioutil.ReadAll(res.Body) + st.Expect(t, string(body)[:13], `{"foo":"bar"}`) + + // Verify that we don't have pending mocks + st.Expect(t, gock.IsDone(), true) +} +``` + +#### Request headers matching + +```go +package test + +import ( + "github.com/nbio/st" + "gopkg.in/h2non/gock.v1" + "io/ioutil" + "net/http" + "testing" +) + +func TestMatchHeaders(t *testing.T) { + defer gock.Off() + + gock.New("http://foo.com"). + MatchHeader("Authorization", "^foo bar$"). + MatchHeader("API", "1.[0-9]+"). + HeaderPresent("Accept"). + Reply(200). + BodyString("foo foo") + + req, err := http.NewRequest("GET", "http://foo.com", nil) + req.Header.Set("Authorization", "foo bar") + req.Header.Set("API", "1.0") + req.Header.Set("Accept", "text/plain") + + res, err := (&http.Client{}).Do(req) + st.Expect(t, err, nil) + st.Expect(t, res.StatusCode, 200) + body, _ := ioutil.ReadAll(res.Body) + st.Expect(t, string(body), "foo foo") + + // Verify that we don't have pending mocks + st.Expect(t, gock.IsDone(), true) +} +``` + +#### Request param matching + +```go +package test + +import ( + "github.com/nbio/st" + "gopkg.in/h2non/gock.v1" + "io/ioutil" + "net/http" + "testing" +) + +func TestMatchParams(t *testing.T) { + defer gock.Off() + + gock.New("http://foo.com"). + MatchParam("page", "1"). + MatchParam("per_page", "10"). + Reply(200). + BodyString("foo foo") + + req, err := http.NewRequest("GET", "http://foo.com?page=1&per_page=10", nil) + + res, err := (&http.Client{}).Do(req) + st.Expect(t, err, nil) + st.Expect(t, res.StatusCode, 200) + body, _ := ioutil.ReadAll(res.Body) + st.Expect(t, string(body), "foo foo") + + // Verify that we don't have pending mocks + st.Expect(t, gock.IsDone(), true) +} +``` + +#### JSON body matching and response + +```go +package test + +import ( + "bytes" + "github.com/nbio/st" + "gopkg.in/h2non/gock.v1" + "io/ioutil" + "net/http" + "testing" +) + +func TestMockSimple(t *testing.T) { + defer gock.Off() + + gock.New("http://foo.com"). + Post("/bar"). + MatchType("json"). + JSON(map[string]string{"foo": "bar"}). + Reply(201). + JSON(map[string]string{"bar": "foo"}) + + body := bytes.NewBuffer([]byte(`{"foo":"bar"}`)) + res, err := http.Post("http://foo.com/bar", "application/json", body) + st.Expect(t, err, nil) + st.Expect(t, res.StatusCode, 201) + + resBody, _ := ioutil.ReadAll(res.Body) + st.Expect(t, string(resBody)[:13], `{"bar":"foo"}`) + + // Verify that we don't have pending mocks + st.Expect(t, gock.IsDone(), true) +} +``` + +#### Mocking a custom http.Client and http.RoundTripper + +```go +package test + +import ( + "github.com/nbio/st" + "gopkg.in/h2non/gock.v1" + "io/ioutil" + "net/http" + "testing" +) + +func TestClient(t *testing.T) { + defer gock.Off() + + gock.New("http://foo.com"). + Reply(200). + BodyString("foo foo") + + req, err := http.NewRequest("GET", "http://foo.com", nil) + client := &http.Client{Transport: &http.Transport{}} + gock.InterceptClient(client) + + res, err := client.Do(req) + st.Expect(t, err, nil) + st.Expect(t, res.StatusCode, 200) + body, _ := ioutil.ReadAll(res.Body) + st.Expect(t, string(body), "foo foo") + + // Verify that we don't have pending mocks + st.Expect(t, gock.IsDone(), true) +} +``` + +#### Enable real networking + +```go +package main + +import ( + "fmt" + "gopkg.in/h2non/gock.v1" + "io/ioutil" + "net/http" +) + +func main() { + defer gock.Off() + defer gock.DisableNetworking() + + gock.EnableNetworking() + gock.New("http://httpbin.org"). + Get("/get"). + Reply(201). + SetHeader("Server", "gock") + + res, err := http.Get("http://httpbin.org/get") + if err != nil { + fmt.Errorf("Error: %s", err) + } + + // The response status comes from the mock + fmt.Printf("Status: %d\n", res.StatusCode) + // The server header comes from mock as well + fmt.Printf("Server header: %s\n", res.Header.Get("Server")) + // Response body is the original + body, _ := ioutil.ReadAll(res.Body) + fmt.Printf("Body: %s", string(body)) +} +``` + +#### Debug intercepted http requests + +```go +package main + +import ( + "bytes" + "gopkg.in/h2non/gock.v1" + "net/http" +) + +func main() { + defer gock.Off() + gock.Observe(gock.DumpRequest) + + gock.New("http://foo.com"). + Post("/bar"). + MatchType("json"). + JSON(map[string]string{"foo": "bar"}). + Reply(200) + + body := bytes.NewBuffer([]byte(`{"foo":"bar"}`)) + http.Post("http://foo.com/bar", "application/json", body) +} + +``` + +## Hacking it! + +You can easily hack `gock` defining custom matcher functions with own matching rules. + +See [add matcher functions](https://github.com/h2non/gock/blob/master/_examples/add_matchers/matchers.go) and [custom matching layer](https://github.com/h2non/gock/blob/master/_examples/custom_matcher/matcher.go) examples for further details. + +## License + +MIT - Tomas Aparicio diff --git a/src/vendor/gopkg.in/h2non/gock.v1/go.mod b/src/vendor/gopkg.in/h2non/gock.v1/go.mod new file mode 100644 index 000000000..98fe8ea35 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/go.mod @@ -0,0 +1,8 @@ +module gopkg.in/h2non/gock.v1 + +require ( + github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 + github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 + golang.org/x/net v0.0.0-20191021144547-ec77196f6094 // indirect + gopkg.in/h2non/gentleman.v1 v1.0.4 +) diff --git a/src/vendor/gopkg.in/h2non/gock.v1/gock.go b/src/vendor/gopkg.in/h2non/gock.v1/gock.go new file mode 100644 index 000000000..c3a8333b5 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/gock.go @@ -0,0 +1,178 @@ +package gock + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "regexp" + "sync" +) + +// mutex is used interally for locking thread-sensitive functions. +var mutex = &sync.Mutex{} + +// config global singleton store. +var config = struct { + Networking bool + NetworkingFilters []FilterRequestFunc + Observer ObserverFunc +}{} + +// ObserverFunc is implemented by users to inspect the outgoing intercepted HTTP traffic +type ObserverFunc func(*http.Request, Mock) + +// DumpRequest is a default implementation of ObserverFunc that dumps +// the HTTP/1.x wire representation of the http request +var DumpRequest ObserverFunc = func(request *http.Request, mock Mock) { + bytes, _ := httputil.DumpRequestOut(request, true) + fmt.Println(string(bytes)) + fmt.Printf("\nMatches: %v\n---\n", mock != nil) +} + +// track unmatched requests so they can be tested for +var unmatchedRequests = []*http.Request{} + +// New creates and registers a new HTTP mock with +// default settings and returns the Request DSL for HTTP mock +// definition and set up. +func New(uri string) *Request { + Intercept() + + res := NewResponse() + req := NewRequest() + req.URLStruct, res.Error = url.Parse(normalizeURI(uri)) + + // Create the new mock expectation + exp := NewMock(req, res) + Register(exp) + + return req +} + +// Intercepting returns true if gock is currently able to intercept. +func Intercepting() bool { + mutex.Lock() + defer mutex.Unlock() + return http.DefaultTransport == DefaultTransport +} + +// Intercept enables HTTP traffic interception via http.DefaultTransport. +// If you are using a custom HTTP transport, you have to use `gock.Transport()` +func Intercept() { + if !Intercepting() { + mutex.Lock() + http.DefaultTransport = DefaultTransport + mutex.Unlock() + } +} + +// InterceptClient allows the developer to intercept HTTP traffic using +// a custom http.Client who uses a non default http.Transport/http.RoundTripper implementation. +func InterceptClient(cli *http.Client) { + _, ok := cli.Transport.(*Transport) + if ok { + return // if transport already intercepted, just ignore it + } + trans := NewTransport() + trans.Transport = cli.Transport + cli.Transport = trans +} + +// RestoreClient allows the developer to disable and restore the +// original transport in the given http.Client. +func RestoreClient(cli *http.Client) { + trans, ok := cli.Transport.(*Transport) + if !ok { + return + } + cli.Transport = trans.Transport +} + +// Disable disables HTTP traffic interception by gock. +func Disable() { + mutex.Lock() + defer mutex.Unlock() + http.DefaultTransport = NativeTransport +} + +// Off disables the default HTTP interceptors and removes +// all the registered mocks, even if they has not been intercepted yet. +func Off() { + Flush() + Disable() +} + +// OffAll is like `Off()`, but it also removes the unmatched requests registry. +func OffAll() { + Flush() + Disable() + CleanUnmatchedRequest() +} + +// Observe provides a hook to support inspection of the request and matched mock +func Observe(fn ObserverFunc) { + mutex.Lock() + defer mutex.Unlock() + config.Observer = fn +} + +// EnableNetworking enables real HTTP networking +func EnableNetworking() { + mutex.Lock() + defer mutex.Unlock() + config.Networking = true +} + +// DisableNetworking disables real HTTP networking +func DisableNetworking() { + mutex.Lock() + defer mutex.Unlock() + config.Networking = false +} + +// NetworkingFilter determines if an http.Request should be triggered or not. +func NetworkingFilter(fn FilterRequestFunc) { + mutex.Lock() + defer mutex.Unlock() + config.NetworkingFilters = append(config.NetworkingFilters, fn) +} + +// DisableNetworkingFilters disables registered networking filters. +func DisableNetworkingFilters() { + mutex.Lock() + defer mutex.Unlock() + config.NetworkingFilters = []FilterRequestFunc{} +} + +// GetUnmatchedRequests returns all requests that have been received but haven't matched any mock +func GetUnmatchedRequests() []*http.Request { + mutex.Lock() + defer mutex.Unlock() + return unmatchedRequests +} + +// HasUnmatchedRequest returns true if gock has received any requests that didn't match a mock +func HasUnmatchedRequest() bool { + return len(GetUnmatchedRequests()) > 0 +} + +// CleanUnmatchedRequest cleans the unmatched requests internal registry. +func CleanUnmatchedRequest() { + mutex.Lock() + defer mutex.Unlock() + unmatchedRequests = []*http.Request{} +} + +func trackUnmatchedRequest(req *http.Request) { + mutex.Lock() + defer mutex.Unlock() + unmatchedRequests = append(unmatchedRequests, req) +} + +func normalizeURI(uri string) string { + if ok, _ := regexp.MatchString("^http[s]?", uri); !ok { + return "http://" + uri + } + return uri +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/matcher.go b/src/vendor/gopkg.in/h2non/gock.v1/matcher.go new file mode 100644 index 000000000..11a1d7eac --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/matcher.go @@ -0,0 +1,137 @@ +package gock + +import "net/http" + +// MatchersHeader exposes an slice of HTTP header specific mock matchers. +var MatchersHeader = []MatchFunc{ + MatchMethod, + MatchScheme, + MatchHost, + MatchPath, + MatchHeaders, + MatchQueryParams, + MatchPathParams, +} + +// MatchersBody exposes an slice of HTTP body specific built-in mock matchers. +var MatchersBody = []MatchFunc{ + MatchBody, +} + +// Matchers stores all the built-in mock matchers. +var Matchers = append(MatchersHeader, MatchersBody...) + +// DefaultMatcher stores the default Matcher instance used to match mocks. +var DefaultMatcher = NewMatcher() + +// MatchFunc represents the required function +// interface implemented by matchers. +type MatchFunc func(*http.Request, *Request) (bool, error) + +// Matcher represents the required interface implemented by mock matchers. +type Matcher interface { + // Get returns a slice of registered function matchers. + Get() []MatchFunc + + // Add adds a new matcher function. + Add(MatchFunc) + + // Set sets the matchers functions stack. + Set([]MatchFunc) + + // Flush flushes the current matchers function stack. + Flush() + + // Match matches the given http.Request with a mock Request. + Match(*http.Request, *Request) (bool, error) +} + +// MockMatcher implements a mock matcher +type MockMatcher struct { + Matchers []MatchFunc +} + +// NewMatcher creates a new mock matcher +// using the default matcher functions. +func NewMatcher() *MockMatcher { + m := NewEmptyMatcher() + for _, matchFn := range Matchers { + m.Add(matchFn) + } + return m +} + +// NewBasicMatcher creates a new matcher with header only mock matchers. +func NewBasicMatcher() *MockMatcher { + m := NewEmptyMatcher() + for _, matchFn := range MatchersHeader { + m.Add(matchFn) + } + return m +} + +// NewEmptyMatcher creates a new empty matcher without default matchers. +func NewEmptyMatcher() *MockMatcher { + return &MockMatcher{Matchers: []MatchFunc{}} +} + +// Get returns a slice of registered function matchers. +func (m *MockMatcher) Get() []MatchFunc { + mutex.Lock() + defer mutex.Unlock() + return m.Matchers +} + +// Add adds a new function matcher. +func (m *MockMatcher) Add(fn MatchFunc) { + m.Matchers = append(m.Matchers, fn) +} + +// Set sets a new stack of matchers functions. +func (m *MockMatcher) Set(stack []MatchFunc) { + m.Matchers = stack +} + +// Flush flushes the current matcher +func (m *MockMatcher) Flush() { + m.Matchers = []MatchFunc{} +} + +// Clone returns a separate MockMatcher instance that has a copy of the same MatcherFuncs +func (m *MockMatcher) Clone() *MockMatcher { + m2 := NewEmptyMatcher() + for _, mFn := range m.Get() { + m2.Add(mFn) + } + return m2 +} + +// Match matches the given http.Request with a mock request +// returning true in case that the request matches, otherwise false. +func (m *MockMatcher) Match(req *http.Request, ereq *Request) (bool, error) { + for _, matcher := range m.Matchers { + matches, err := matcher(req, ereq) + if err != nil { + return false, err + } + if !matches { + return false, nil + } + } + return true, nil +} + +// MatchMock is a helper function that matches the given http.Request +// in the list of registered mocks, returning it if matches or error if it fails. +func MatchMock(req *http.Request) (Mock, error) { + for _, mock := range GetAll() { + matches, err := mock.Match(req) + if err != nil { + return nil, err + } + if matches { + return mock, nil + } + } + return nil, nil +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/matchers.go b/src/vendor/gopkg.in/h2non/gock.v1/matchers.go new file mode 100644 index 000000000..7b2b3bf0a --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/matchers.go @@ -0,0 +1,261 @@ +package gock + +import ( + "compress/gzip" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "reflect" + "regexp" + "strings" + + "github.com/h2non/parth" +) + +// EOL represents the end of line character. +const EOL = 0xa + +// BodyTypes stores the supported MIME body types for matching. +// Currently only text-based types. +var BodyTypes = []string{ + "text/html", + "text/plain", + "application/json", + "application/xml", + "multipart/form-data", + "application/x-www-form-urlencoded", +} + +// BodyTypeAliases stores a generic MIME type by alias. +var BodyTypeAliases = map[string]string{ + "html": "text/html", + "text": "text/plain", + "json": "application/json", + "xml": "application/xml", + "form": "multipart/form-data", + "url": "application/x-www-form-urlencoded", +} + +// CompressionSchemes stores the supported Content-Encoding types for decompression. +var CompressionSchemes = []string{ + "gzip", +} + +// MatchMethod matches the HTTP method of the given request. +func MatchMethod(req *http.Request, ereq *Request) (bool, error) { + return ereq.Method == "" || req.Method == ereq.Method, nil +} + +// MatchScheme matches the request URL protocol scheme. +func MatchScheme(req *http.Request, ereq *Request) (bool, error) { + return ereq.URLStruct.Scheme == "" || req.URL.Scheme == "" || ereq.URLStruct.Scheme == req.URL.Scheme, nil +} + +// MatchHost matches the HTTP host header field of the given request. +func MatchHost(req *http.Request, ereq *Request) (bool, error) { + url := ereq.URLStruct + if strings.EqualFold(url.Host, req.URL.Host) { + return true, nil + } + if !ereq.Options.DisableRegexpHost { + return regexp.MatchString(url.Host, req.URL.Host) + } + return false, nil +} + +// MatchPath matches the HTTP URL path of the given request. +func MatchPath(req *http.Request, ereq *Request) (bool, error) { + if req.URL.Path == ereq.URLStruct.Path { + return true, nil + } + return regexp.MatchString(ereq.URLStruct.Path, req.URL.Path) +} + +// MatchHeaders matches the headers fields of the given request. +func MatchHeaders(req *http.Request, ereq *Request) (bool, error) { + for key, value := range ereq.Header { + var err error + var match bool + var matchEscaped bool + + for _, field := range req.Header[key] { + match, err = regexp.MatchString(value[0], field) + //Some values may contain reserved regex params e.g. "()", try matching with these escaped + matchEscaped, err = regexp.MatchString(regexp.QuoteMeta(value[0]), field) + + if err != nil { + return false, err + } + if match || matchEscaped { + break + } + + } + + if !match && !matchEscaped { + return false, nil + } + } + return true, nil +} + +// MatchQueryParams matches the URL query params fields of the given request. +func MatchQueryParams(req *http.Request, ereq *Request) (bool, error) { + for key, value := range ereq.URLStruct.Query() { + var err error + var match bool + + for _, field := range req.URL.Query()[key] { + match, err = regexp.MatchString(value[0], field) + if err != nil { + return false, err + } + if match { + break + } + } + + if !match { + return false, nil + } + } + return true, nil +} + +// MatchPathParams matches the URL path parameters of the given request. +func MatchPathParams(req *http.Request, ereq *Request) (bool, error) { + for key, value := range ereq.PathParams { + var s string + + if err := parth.Sequent(req.URL.Path, key, &s); err != nil { + return false, nil + } + + if s != value { + return false, nil + } + } + return true, nil +} + +// MatchBody tries to match the request body. +// TODO: not too smart now, needs several improvements. +func MatchBody(req *http.Request, ereq *Request) (bool, error) { + // If match body is empty, just continue + if req.Method == "HEAD" || len(ereq.BodyBuffer) == 0 { + return true, nil + } + + // Only can match certain MIME body types + if !supportedType(req) { + return false, nil + } + + // Can only match certain compression schemes + if !supportedCompressionScheme(req) { + return false, nil + } + + // Create a reader for the body depending on compression type + bodyReader := req.Body + if ereq.CompressionScheme != "" { + if ereq.CompressionScheme != req.Header.Get("Content-Encoding") { + return false, nil + } + compressedBodyReader, err := compressionReader(req.Body, ereq.CompressionScheme) + if err != nil { + return false, err + } + bodyReader = compressedBodyReader + } + + // Read the whole request body + body, err := ioutil.ReadAll(bodyReader) + if err != nil { + return false, err + } + + // Restore body reader stream + req.Body = createReadCloser(body) + + // If empty, ignore the match + if len(body) == 0 && len(ereq.BodyBuffer) != 0 { + return false, nil + } + + // Match body by atomic string comparison + bodyStr := castToString(body) + matchStr := castToString(ereq.BodyBuffer) + if bodyStr == matchStr { + return true, nil + } + + // Match request body by regexp + match, _ := regexp.MatchString(matchStr, bodyStr) + if match == true { + return true, nil + } + + // todo - add conditional do only perform the conversion of body bytes + // representation of JSON to a map and then compare them for equality. + + // Check if the key + value pairs match + var bodyMap map[string]interface{} + var matchMap map[string]interface{} + + // Ensure that both byte bodies that that should be JSON can be converted to maps. + umErr := json.Unmarshal(body, &bodyMap) + umErr2 := json.Unmarshal(ereq.BodyBuffer, &matchMap) + if umErr == nil && umErr2 == nil && reflect.DeepEqual(bodyMap, matchMap) { + return true, nil + } + + return false, nil +} + +func supportedType(req *http.Request) bool { + mime := req.Header.Get("Content-Type") + if mime == "" { + return true + } + + for _, kind := range BodyTypes { + if match, _ := regexp.MatchString(kind, mime); match { + return true + } + } + return false +} + +func supportedCompressionScheme(req *http.Request) bool { + encoding := req.Header.Get("Content-Encoding") + if encoding == "" { + return true + } + + for _, kind := range CompressionSchemes { + if match, _ := regexp.MatchString(kind, encoding); match { + return true + } + } + return false +} + +func castToString(buf []byte) string { + str := string(buf) + tail := len(str) - 1 + if str[tail] == EOL { + str = str[:tail] + } + return str +} + +func compressionReader(r io.ReadCloser, scheme string) (io.ReadCloser, error) { + switch scheme { + case "gzip": + return gzip.NewReader(r) + default: + return r, nil + } +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/mock.go b/src/vendor/gopkg.in/h2non/gock.v1/mock.go new file mode 100644 index 000000000..75ad59230 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/mock.go @@ -0,0 +1,146 @@ +package gock + +import ( + "net/http" + "sync" +) + +// Mock represents the required interface that must +// be implemented by HTTP mock instances. +type Mock interface { + // Disable disables the current mock manually. + Disable() + + // Done returns true if the current mock is disabled. + Done() bool + + // Request returns the mock Request instance. + Request() *Request + + // Response returns the mock Response instance. + Response() *Response + + // Match matches the given http.Request with the current mock. + Match(*http.Request) (bool, error) + + // AddMatcher adds a new matcher function. + AddMatcher(MatchFunc) + + // SetMatcher uses a new matcher implementation. + SetMatcher(Matcher) +} + +// Mocker implements a Mock capable interface providing +// a default mock configuration used internally to store mocks. +type Mocker struct { + // disabled stores if the current mock is disabled. + disabled bool + + // mutex stores the mock mutex for thread safity. + mutex sync.Mutex + + // matcher stores a Matcher capable instance to match the given http.Request. + matcher Matcher + + // request stores the mock Request to match. + request *Request + + // response stores the mock Response to use in case of match. + response *Response +} + +// NewMock creates a new HTTP mock based on the given request and response instances. +// It's mostly used internally. +func NewMock(req *Request, res *Response) *Mocker { + mock := &Mocker{ + request: req, + response: res, + matcher: DefaultMatcher.Clone(), + } + res.Mock = mock + req.Mock = mock + req.Response = res + return mock +} + +// Disable disables the current mock manually. +func (m *Mocker) Disable() { + m.disabled = true +} + +// Done returns true in case that the current mock +// instance is disabled and therefore must be removed. +func (m *Mocker) Done() bool { + m.mutex.Lock() + defer m.mutex.Unlock() + return m.disabled || (!m.request.Persisted && m.request.Counter == 0) +} + +// Request returns the Request instance +// configured for the current HTTP mock. +func (m *Mocker) Request() *Request { + return m.request +} + +// Response returns the Response instance +// configured for the current HTTP mock. +func (m *Mocker) Response() *Response { + return m.response +} + +// Match matches the given http.Request with the current Request +// mock expectation, returning true if matches. +func (m *Mocker) Match(req *http.Request) (bool, error) { + if m.disabled { + return false, nil + } + + // Filter + for _, filter := range m.request.Filters { + if !filter(req) { + return false, nil + } + } + + // Map + for _, mapper := range m.request.Mappers { + if treq := mapper(req); treq != nil { + req = treq + } + } + + // Match + matches, err := m.matcher.Match(req, m.request) + if matches { + m.decrement() + } + + return matches, err +} + +// SetMatcher sets a new matcher implementation +// for the current mock expectation. +func (m *Mocker) SetMatcher(matcher Matcher) { + m.matcher = matcher +} + +// AddMatcher adds a new matcher function +// for the current mock expectation. +func (m *Mocker) AddMatcher(fn MatchFunc) { + m.matcher.Add(fn) +} + +// decrement decrements the current mock Request counter. +func (m *Mocker) decrement() { + if m.request.Persisted { + return + } + + m.mutex.Lock() + defer m.mutex.Unlock() + + m.request.Counter-- + if m.request.Counter == 0 { + m.disabled = true + } +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/options.go b/src/vendor/gopkg.in/h2non/gock.v1/options.go new file mode 100644 index 000000000..188aa58d8 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/options.go @@ -0,0 +1,8 @@ +package gock + +// Options represents customized option for gock +type Options struct { + // DisableRegexpHost stores if the host is only a plain string rather than regular expression, + // if DisableRegexpHost is true, host sets in gock.New(...) will be treated as plain string + DisableRegexpHost bool +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/request.go b/src/vendor/gopkg.in/h2non/gock.v1/request.go new file mode 100644 index 000000000..c2edb21b5 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/request.go @@ -0,0 +1,325 @@ +package gock + +import ( + "encoding/base64" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// MapRequestFunc represents the required function interface for request mappers. +type MapRequestFunc func(*http.Request) *http.Request + +// FilterRequestFunc represents the required function interface for request filters. +type FilterRequestFunc func(*http.Request) bool + +// Request represents the high-level HTTP request used to store +// request fields used to match intercepted requests. +type Request struct { + // Mock stores the parent mock reference for the current request mock used for method delegation. + Mock Mock + + // Response stores the current Response instance for the current matches Request. + Response *Response + + // Error stores the latest mock request configuration error. + Error error + + // Counter stores the pending times that the current mock should be active. + Counter int + + // Persisted stores if the current mock should be always active. + Persisted bool + + // Options stores options for current Request. + Options Options + + // URLStruct stores the parsed URL as *url.URL struct. + URLStruct *url.URL + + // Method stores the Request HTTP method to match. + Method string + + // CompressionScheme stores the Request Compression scheme to match and use for decompression. + CompressionScheme string + + // Header stores the HTTP header fields to match. + Header http.Header + + // Cookies stores the Request HTTP cookies values to match. + Cookies []*http.Cookie + + // PathParams stores the path parameters to match. + PathParams map[string]string + + // BodyBuffer stores the body data to match. + BodyBuffer []byte + + // Mappers stores the request functions mappers used for matching. + Mappers []MapRequestFunc + + // Filters stores the request functions filters used for matching. + Filters []FilterRequestFunc +} + +// NewRequest creates a new Request instance. +func NewRequest() *Request { + return &Request{ + Counter: 1, + URLStruct: &url.URL{}, + Header: make(http.Header), + PathParams: make(map[string]string), + } +} + +// URL defines the mock URL to match. +func (r *Request) URL(uri string) *Request { + r.URLStruct, r.Error = url.Parse(uri) + return r +} + +// SetURL defines the url.URL struct to be used for matching. +func (r *Request) SetURL(u *url.URL) *Request { + r.URLStruct = u + return r +} + +// Path defines the mock URL path value to match. +func (r *Request) Path(path string) *Request { + r.URLStruct.Path = path + return r +} + +// Get specifies the GET method and the given URL path to match. +func (r *Request) Get(path string) *Request { + return r.method("GET", path) +} + +// Post specifies the POST method and the given URL path to match. +func (r *Request) Post(path string) *Request { + return r.method("POST", path) +} + +// Put specifies the PUT method and the given URL path to match. +func (r *Request) Put(path string) *Request { + return r.method("PUT", path) +} + +// Delete specifies the DELETE method and the given URL path to match. +func (r *Request) Delete(path string) *Request { + return r.method("DELETE", path) +} + +// Patch specifies the PATCH method and the given URL path to match. +func (r *Request) Patch(path string) *Request { + return r.method("PATCH", path) +} + +// Head specifies the HEAD method and the given URL path to match. +func (r *Request) Head(path string) *Request { + return r.method("HEAD", path) +} + +// method is a DRY shortcut used to declare the expected HTTP method and URL path. +func (r *Request) method(method, path string) *Request { + if path != "/" { + r.URLStruct.Path = path + } + r.Method = strings.ToUpper(method) + return r +} + +// Body defines the body data to match based on a io.Reader interface. +func (r *Request) Body(body io.Reader) *Request { + r.BodyBuffer, r.Error = ioutil.ReadAll(body) + return r +} + +// BodyString defines the body to match based on a given string. +func (r *Request) BodyString(body string) *Request { + r.BodyBuffer = []byte(body) + return r +} + +// File defines the body to match based on the given file path string. +func (r *Request) File(path string) *Request { + r.BodyBuffer, r.Error = ioutil.ReadFile(path) + return r +} + +// Compression defines the request compression scheme, and enables automatic body decompression. +// Supports only the "gzip" scheme so far. +func (r *Request) Compression(scheme string) *Request { + r.Header.Set("Content-Encoding", scheme) + r.CompressionScheme = scheme + return r +} + +// JSON defines the JSON body to match based on a given structure. +func (r *Request) JSON(data interface{}) *Request { + if r.Header.Get("Content-Type") == "" { + r.Header.Set("Content-Type", "application/json") + } + r.BodyBuffer, r.Error = readAndDecode(data, "json") + return r +} + +// XML defines the XML body to match based on a given structure. +func (r *Request) XML(data interface{}) *Request { + if r.Header.Get("Content-Type") == "" { + r.Header.Set("Content-Type", "application/xml") + } + r.BodyBuffer, r.Error = readAndDecode(data, "xml") + return r +} + +// MatchType defines the request Content-Type MIME header field. +// Supports type alias. E.g: json, xml, form, text... +func (r *Request) MatchType(kind string) *Request { + mime := BodyTypeAliases[kind] + if mime != "" { + kind = mime + } + r.Header.Set("Content-Type", kind) + return r +} + +// BasicAuth defines a username and password for HTTP Basic Authentication +func (r *Request) BasicAuth(username, password string) *Request { + r.Header.Set("Authorization", "Basic "+basicAuth(username, password)) + return r +} + +// MatchHeader defines a new key and value header to match. +func (r *Request) MatchHeader(key, value string) *Request { + r.Header[key] = []string{value} + return r +} + +// HeaderPresent defines that a header field must be present in the request. +func (r *Request) HeaderPresent(key string) *Request { + r.Header[key] = []string{".*"} + return r +} + +// MatchHeaders defines a map of key-value headers to match. +func (r *Request) MatchHeaders(headers map[string]string) *Request { + for key, value := range headers { + r.Header[key] = []string{value} + } + return r +} + +// MatchParam defines a new key and value URL query param to match. +func (r *Request) MatchParam(key, value string) *Request { + query := r.URLStruct.Query() + query.Set(key, value) + r.URLStruct.RawQuery = query.Encode() + return r +} + +// MatchParams defines a map of URL query param key-value to match. +func (r *Request) MatchParams(params map[string]string) *Request { + query := r.URLStruct.Query() + for key, value := range params { + query.Set(key, value) + } + r.URLStruct.RawQuery = query.Encode() + return r +} + +// ParamPresent matches if the given query param key is present in the URL. +func (r *Request) ParamPresent(key string) *Request { + r.MatchParam(key, ".*") + return r +} + +// PathParam matches if a given path parameter key is present in the URL. +// +// The value is representative of the restful resource the key defines, e.g. +// // /users/123/name +// r.PathParam("users", "123") +// would match. +func (r *Request) PathParam(key, val string) *Request { + r.PathParams[key] = val + + return r +} + +// Persist defines the current HTTP mock as persistent and won't be removed after intercepting it. +func (r *Request) Persist() *Request { + r.Persisted = true + return r +} + +// WithOptions sets the options for the request. +func (r *Request) WithOptions(options Options) *Request { + r.Options = options + return r +} + +// Times defines the number of times that the current HTTP mock should remain active. +func (r *Request) Times(num int) *Request { + r.Counter = num + return r +} + +// AddMatcher adds a new matcher function to match the request. +func (r *Request) AddMatcher(fn MatchFunc) *Request { + r.Mock.AddMatcher(fn) + return r +} + +// SetMatcher sets a new matcher function to match the request. +func (r *Request) SetMatcher(matcher Matcher) *Request { + r.Mock.SetMatcher(matcher) + return r +} + +// Map adds a new request mapper function to map http.Request before the matching process. +func (r *Request) Map(fn MapRequestFunc) *Request { + r.Mappers = append(r.Mappers, fn) + return r +} + +// Filter filters a new request filter function to filter http.Request before the matching process. +func (r *Request) Filter(fn FilterRequestFunc) *Request { + r.Filters = append(r.Filters, fn) + return r +} + +// EnableNetworking enables the use real networking for the current mock. +func (r *Request) EnableNetworking() *Request { + if r.Response != nil { + r.Response.UseNetwork = true + } + return r +} + +// Reply defines the Response status code and returns the mock Response DSL. +func (r *Request) Reply(status int) *Response { + return r.Response.Status(status) +} + +// ReplyError defines the Response simulated error. +func (r *Request) ReplyError(err error) *Response { + return r.Response.SetError(err) +} + +// ReplyFunc allows the developer to define the mock response via a custom function. +func (r *Request) ReplyFunc(replier func(*Response)) *Response { + replier(r.Response) + return r.Response +} + +// See 2 (end of page 4) https://www.ietf.org/rfc/rfc2617.txt +// "To receive authorization, the client sends the userid and password, +// separated by a single colon (":") character, within a base64 +// encoded string in the credentials." +// It is not meant to be urlencoded. +func basicAuth(username, password string) string { + auth := username + ":" + password + return base64.StdEncoding.EncodeToString([]byte(auth)) +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/responder.go b/src/vendor/gopkg.in/h2non/gock.v1/responder.go new file mode 100644 index 000000000..35eb87191 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/responder.go @@ -0,0 +1,87 @@ +package gock + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "strconv" + "time" +) + +// Responder builds a mock http.Response based on the given Response mock. +func Responder(req *http.Request, mock *Response, res *http.Response) (*http.Response, error) { + // If error present, reply it + err := mock.Error + if err != nil { + return nil, err + } + + if res == nil { + res = createResponse(req) + } + + // Apply response filter + for _, filter := range mock.Filters { + if !filter(res) { + return res, nil + } + } + + // Define mock status code + if mock.StatusCode != 0 { + res.Status = strconv.Itoa(mock.StatusCode) + " " + http.StatusText(mock.StatusCode) + res.StatusCode = mock.StatusCode + } + + // Define headers by merging fields + res.Header = mergeHeaders(res, mock) + + // Define mock body, if present + if len(mock.BodyBuffer) > 0 { + res.ContentLength = int64(len(mock.BodyBuffer)) + res.Body = createReadCloser(mock.BodyBuffer) + } + + // Apply response mappers + for _, mapper := range mock.Mappers { + if tres := mapper(res); tres != nil { + res = tres + } + } + + // Sleep to simulate delay, if necessary + if mock.ResponseDelay > 0 { + time.Sleep(mock.ResponseDelay) + } + + return res, err +} + +// createResponse creates a new http.Response with default fields. +func createResponse(req *http.Request) *http.Response { + return &http.Response{ + ProtoMajor: 1, + ProtoMinor: 1, + Proto: "HTTP/1.1", + Request: req, + Header: make(http.Header), + Body: createReadCloser([]byte{}), + } +} + +// mergeHeaders copies the mock headers. +func mergeHeaders(res *http.Response, mres *Response) http.Header { + for key, values := range mres.Header { + for _, value := range values { + res.Header.Add(key, value) + } + } + return res.Header +} + +// createReadCloser creates an io.ReadCloser from a byte slice that is suitable for use as an +// http response body. +func createReadCloser(body []byte) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader(body)) +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/response.go b/src/vendor/gopkg.in/h2non/gock.v1/response.go new file mode 100644 index 000000000..07b715809 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/response.go @@ -0,0 +1,186 @@ +package gock + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "io" + "io/ioutil" + "net/http" + "time" +) + +// MapResponseFunc represents the required function interface impletemed by response mappers. +type MapResponseFunc func(*http.Response) *http.Response + +// FilterResponseFunc represents the required function interface impletemed by response filters. +type FilterResponseFunc func(*http.Response) bool + +// Response represents high-level HTTP fields to configure +// and define HTTP responses intercepted by gock. +type Response struct { + // Mock stores the parent mock reference for the current response mock used for method delegation. + Mock Mock + + // Error stores the latest response configuration or injected error. + Error error + + // UseNetwork enables the use of real network for the current mock. + UseNetwork bool + + // StatusCode stores the response status code. + StatusCode int + + // Headers stores the response headers. + Header http.Header + + // Cookies stores the response cookie fields. + Cookies []*http.Cookie + + // BodyBuffer stores the array of bytes to use as body. + BodyBuffer []byte + + // ResponseDelay stores the simulated response delay. + ResponseDelay time.Duration + + // Mappers stores the request functions mappers used for matching. + Mappers []MapResponseFunc + + // Filters stores the request functions filters used for matching. + Filters []FilterResponseFunc +} + +// NewResponse creates a new Response. +func NewResponse() *Response { + return &Response{Header: make(http.Header)} +} + +// Status defines the desired HTTP status code to reply in the current response. +func (r *Response) Status(code int) *Response { + r.StatusCode = code + return r +} + +// Type defines the response Content-Type MIME header field. +// Supports type alias. E.g: json, xml, form, text... +func (r *Response) Type(kind string) *Response { + mime := BodyTypeAliases[kind] + if mime != "" { + kind = mime + } + r.Header.Set("Content-Type", kind) + return r +} + +// SetHeader sets a new header field in the mock response. +func (r *Response) SetHeader(key, value string) *Response { + r.Header.Set(key, value) + return r +} + +// AddHeader adds a new header field in the mock response +// with out removing an existent one. +func (r *Response) AddHeader(key, value string) *Response { + r.Header.Add(key, value) + return r +} + +// SetHeaders sets a map of header fields in the mock response. +func (r *Response) SetHeaders(headers map[string]string) *Response { + for key, value := range headers { + r.Header.Add(key, value) + } + return r +} + +// Body sets the HTTP response body to be used. +func (r *Response) Body(body io.Reader) *Response { + r.BodyBuffer, r.Error = ioutil.ReadAll(body) + return r +} + +// BodyString defines the response body as string. +func (r *Response) BodyString(body string) *Response { + r.BodyBuffer = []byte(body) + return r +} + +// File defines the response body reading the data +// from disk based on the file path string. +func (r *Response) File(path string) *Response { + r.BodyBuffer, r.Error = ioutil.ReadFile(path) + return r +} + +// JSON defines the response body based on a JSON based input. +func (r *Response) JSON(data interface{}) *Response { + r.Header.Set("Content-Type", "application/json") + r.BodyBuffer, r.Error = readAndDecode(data, "json") + return r +} + +// XML defines the response body based on a XML based input. +func (r *Response) XML(data interface{}) *Response { + r.Header.Set("Content-Type", "application/xml") + r.BodyBuffer, r.Error = readAndDecode(data, "xml") + return r +} + +// SetError defines the response simulated error. +func (r *Response) SetError(err error) *Response { + r.Error = err + return r +} + +// Delay defines the response simulated delay. +// This feature is still experimental and will be improved in the future. +func (r *Response) Delay(delay time.Duration) *Response { + r.ResponseDelay = delay + return r +} + +// Map adds a new response mapper function to map http.Response before the matching process. +func (r *Response) Map(fn MapResponseFunc) *Response { + r.Mappers = append(r.Mappers, fn) + return r +} + +// Filter filters a new request filter function to filter http.Request before the matching process. +func (r *Response) Filter(fn FilterResponseFunc) *Response { + r.Filters = append(r.Filters, fn) + return r +} + +// EnableNetworking enables the use real networking for the current mock. +func (r *Response) EnableNetworking() *Response { + r.UseNetwork = true + return r +} + +// Done returns true if the mock was done and disabled. +func (r *Response) Done() bool { + return r.Mock.Done() +} + +func readAndDecode(data interface{}, kind string) ([]byte, error) { + buf := &bytes.Buffer{} + + switch data.(type) { + case string: + buf.WriteString(data.(string)) + case []byte: + buf.Write(data.([]byte)) + default: + var err error + if kind == "xml" { + err = xml.NewEncoder(buf).Encode(data) + } else { + err = json.NewEncoder(buf).Encode(data) + } + if err != nil { + return nil, err + } + } + + return ioutil.ReadAll(buf) +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/store.go b/src/vendor/gopkg.in/h2non/gock.v1/store.go new file mode 100644 index 000000000..fc3207e5b --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/store.go @@ -0,0 +1,100 @@ +package gock + +import ( + "sync" +) + +// storeMutex is used interally for store synchronization. +var storeMutex = sync.RWMutex{} + +// mocks is internally used to store registered mocks. +var mocks = []Mock{} + +// Register registers a new mock in the current mocks stack. +func Register(mock Mock) { + if Exists(mock) { + return + } + + // Make ops thread safe + storeMutex.Lock() + defer storeMutex.Unlock() + + // Expose mock in request/response for delegation + mock.Request().Mock = mock + mock.Response().Mock = mock + + // Registers the mock in the global store + mocks = append(mocks, mock) +} + +// GetAll returns the current stack of registed mocks. +func GetAll() []Mock { + storeMutex.RLock() + defer storeMutex.RUnlock() + return mocks +} + +// Exists checks if the given Mock is already registered. +func Exists(m Mock) bool { + storeMutex.RLock() + defer storeMutex.RUnlock() + for _, mock := range mocks { + if mock == m { + return true + } + } + return false +} + +// Remove removes a registered mock by reference. +func Remove(m Mock) { + for i, mock := range mocks { + if mock == m { + storeMutex.Lock() + mocks = append(mocks[:i], mocks[i+1:]...) + storeMutex.Unlock() + } + } +} + +// Flush flushes the current stack of registered mocks. +func Flush() { + storeMutex.Lock() + defer storeMutex.Unlock() + mocks = []Mock{} +} + +// Pending returns an slice of pending mocks. +func Pending() []Mock { + Clean() + storeMutex.RLock() + defer storeMutex.RUnlock() + return mocks +} + +// IsDone returns true if all the registered mocks has been triggered successfully. +func IsDone() bool { + return !IsPending() +} + +// IsPending returns true if there are pending mocks. +func IsPending() bool { + return len(Pending()) > 0 +} + +// Clean cleans the mocks store removing disabled or obsolete mocks. +func Clean() { + storeMutex.Lock() + defer storeMutex.Unlock() + + buf := []Mock{} + for _, mock := range mocks { + if mock.Done() { + continue + } + buf = append(buf, mock) + } + + mocks = buf +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/transport.go b/src/vendor/gopkg.in/h2non/gock.v1/transport.go new file mode 100644 index 000000000..5b2bba283 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/transport.go @@ -0,0 +1,112 @@ +package gock + +import ( + "errors" + "net/http" + "sync" +) + +// var mutex *sync.Mutex = &sync.Mutex{} + +var ( + // DefaultTransport stores the default mock transport used by gock. + DefaultTransport = NewTransport() + + // NativeTransport stores the native net/http default transport + // in order to restore it when needed. + NativeTransport = http.DefaultTransport +) + +var ( + // ErrCannotMatch store the error returned in case of no matches. + ErrCannotMatch = errors.New("gock: cannot match any request") +) + +// Transport implements http.RoundTripper, which fulfills single http requests issued by +// an http.Client. +// +// gock's Transport encapsulates a given or default http.Transport for further +// delegation, if needed. +type Transport struct { + // mutex is used to make transport thread-safe of concurrent uses across goroutines. + mutex sync.Mutex + + // Transport encapsulates the original http.RoundTripper transport interface for delegation. + Transport http.RoundTripper +} + +// NewTransport creates a new *Transport with no responders. +func NewTransport() *Transport { + return &Transport{Transport: NativeTransport} +} + +// RoundTrip receives HTTP requests and routes them to the appropriate responder. It is required to +// implement the http.RoundTripper interface. You will not interact with this directly, instead +// the *http.Client you are using will call it for you. +func (m *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + // Just act as a proxy if not intercepting + if !Intercepting() { + return m.Transport.RoundTrip(req) + } + + m.mutex.Lock() + defer Clean() + + var err error + var res *http.Response + + // Match mock for the incoming http.Request + mock, err := MatchMock(req) + if err != nil { + m.mutex.Unlock() + return nil, err + } + + // Invoke the observer with the intercepted http.Request and matched mock + if config.Observer != nil { + config.Observer(req, mock) + } + + // Verify if should use real networking + networking := shouldUseNetwork(req, mock) + if !networking && mock == nil { + m.mutex.Unlock() + trackUnmatchedRequest(req) + return nil, ErrCannotMatch + } + + // Ensure me unlock the mutex before building the response + m.mutex.Unlock() + + // Perform real networking via original transport + if networking { + res, err = m.Transport.RoundTrip(req) + // In no mock matched, continue with the response + if err != nil || mock == nil { + return res, err + } + } + + return Responder(req, mock.Response(), res) +} + +// CancelRequest is a no-op function. +func (m *Transport) CancelRequest(req *http.Request) {} + +func shouldUseNetwork(req *http.Request, mock Mock) bool { + if mock != nil && mock.Response().UseNetwork { + return true + } + if !config.Networking { + return false + } + if len(config.NetworkingFilters) == 0 { + return true + } + for _, filter := range config.NetworkingFilters { + if !filter(req) { + return false + } + } + return true +} diff --git a/src/vendor/gopkg.in/h2non/gock.v1/version.go b/src/vendor/gopkg.in/h2non/gock.v1/version.go new file mode 100644 index 000000000..8422894a8 --- /dev/null +++ b/src/vendor/gopkg.in/h2non/gock.v1/version.go @@ -0,0 +1,4 @@ +package gock + +// Version defines the current package semantic version. +const Version = "1.0.16" diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index ac37ce61e..bdda78c0c 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -342,6 +342,8 @@ github.com/graph-gophers/dataloader # github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 github.com/gregjones/httpcache github.com/gregjones/httpcache/diskcache +# github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 +github.com/h2non/parth # github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.0 @@ -673,6 +675,9 @@ google.golang.org/protobuf/types/known/timestamppb ## explicit # gopkg.in/gorethink/gorethink.v3 v3.0.5 ## explicit +# gopkg.in/h2non/gock.v1 v1.0.16 +## explicit +gopkg.in/h2non/gock.v1 # gopkg.in/inf.v0 v0.9.1 gopkg.in/inf.v0 # gopkg.in/ini.v1 v1.51.0