Merge pull request #2573 from aledbf/update-go-deps

Refactor e2e tests and update go dependencies
This commit is contained in:
k8s-ci-robot 2018-05-27 22:19:30 -07:00 committed by GitHub
commit c34aeedb53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1625 changed files with 86414 additions and 284401 deletions

View file

@ -18,7 +18,7 @@ notifications:
on_success: never
go:
- 1.10.1
- 1.10.2
go_import_path: k8s.io/ingress-nginx

140
Gopkg.lock generated
View file

@ -4,8 +4,8 @@
[[projects]]
name = "cloud.google.com/go"
packages = ["compute/metadata"]
revision = "2d3a6656c17a60b0815b7e06ab0be04eacb6e613"
version = "v0.16.0"
revision = "0fd7230b2a7505833d5f69b75cbd6c9582401479"
version = "v0.23.0"
[[projects]]
name = "github.com/Azure/go-autorest"
@ -15,20 +15,19 @@
"autorest/azure",
"autorest/date"
]
revision = "ab5671379918d9af294b6f0e3d8aaa98c829416d"
version = "v9.4.0"
revision = "d4e6b95c12a08b4de2d48b45d5b4d594e5d32fab"
[[projects]]
branch = "master"
name = "github.com/armon/go-proxyproto"
packages = ["."]
revision = "48572f11356f1843b694f21a290d4f1006bc5e47"
revision = "5b7edb60ff5f69b60d1950397f7bde6171f1807d"
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
name = "github.com/davecgh/go-spew"
@ -39,8 +38,8 @@
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
version = "v3.1.0"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
name = "github.com/docker/distribution"
@ -74,14 +73,14 @@
[[projects]]
name = "github.com/fsnotify/fsnotify"
packages = ["."]
revision = "629574ca2a5df945712d3079857300b5e4da0236"
version = "v1.4.2"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
branch = "master"
name = "github.com/fullsailor/pkcs7"
packages = ["."]
revision = "a009d8d7de53d9503c797cb8ec66fa3b21eed209"
revision = "ae226422660e5ca10db350d33f81c6608f3fbcdd"
[[projects]]
name = "github.com/ghodss/yaml"
@ -97,8 +96,8 @@
"protoc-gen-gogo/descriptor",
"sortkeys"
]
revision = "342cbe0a04158f6dcb03ca0079991a51a4248c02"
version = "v0.5"
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
[[projects]]
branch = "master"
@ -110,10 +109,9 @@
branch = "master"
name = "github.com/golang/groupcache"
packages = ["lru"]
revision = "84a468cf14b4376def5d68c722b139b881c450a4"
revision = "24b0969c4cb722950103eed87108c8d291a8df00"
[[projects]]
branch = "master"
name = "github.com/golang/protobuf"
packages = [
"proto",
@ -122,7 +120,8 @@
"ptypes/duration",
"ptypes/timestamp"
]
revision = "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
branch = "master"
@ -137,8 +136,8 @@
"compiler",
"extensions"
]
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
version = "v0.1.0"
revision = "7c663266750e7d82587642f65e60bc4083f1f84e"
version = "v0.2.0"
[[projects]]
branch = "master"
@ -152,7 +151,7 @@
"openstack/utils",
"pagination"
]
revision = "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362"
revision = "282f25e4025de0a42015d2e2b5faef1d920aad3c"
[[projects]]
branch = "master"
@ -161,7 +160,7 @@
".",
"simplelru"
]
revision = "0a025b7e63adc15a622f29b0b2c4c3848243bbf6"
revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3"
[[projects]]
branch = "master"
@ -178,14 +177,14 @@
[[projects]]
name = "github.com/json-iterator/go"
packages = ["."]
revision = "6240e1e7983a85228f7fd9c3e1b6932d46ec58e2"
version = "1.0.3"
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
version = "1.1.3"
[[projects]]
name = "github.com/kr/pty"
packages = ["."]
revision = "95d05c1eef33a45bd58676b6ce28d105839b8d0b"
version = "v1.0.1"
revision = "282ce0e5322c82529687d609ee670fac7c7d917c"
version = "v1.1.1"
[[projects]]
branch = "master"
@ -212,7 +211,19 @@
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "06020f85339e21b2478f756a78e295255ffa4d6a"
revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
[[projects]]
name = "github.com/modern-go/concurrent"
packages = ["."]
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
name = "github.com/modern-go/reflect2"
packages = ["."]
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
version = "1.0.0"
[[projects]]
branch = "master"
@ -252,8 +263,8 @@
"reporters/stenographer/support/go-isatty",
"types"
]
revision = "9eda700730cba42af70d53180f9dcce9266bc2bc"
version = "v1.4.0"
revision = "fa5fabab2a1bfbd924faf4c067d07ae414e2aedf"
version = "v1.5.0"
[[projects]]
name = "github.com/onsi/gomega"
@ -271,8 +282,8 @@
"matchers/support/goraph/util",
"types"
]
revision = "c893efa28eb45626cdaa76c9f653b62488858837"
version = "v1.2.0"
revision = "62bff4df71bdbc266561a0caee19f0594b17c240"
version = "v1.4.0"
[[projects]]
name = "github.com/opencontainers/go-digest"
@ -317,7 +328,7 @@
branch = "master"
name = "github.com/prometheus/client_model"
packages = ["go"]
revision = "6f3806018612930941127f2a7c6c453ba2c527d2"
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
@ -327,16 +338,18 @@
"internal/bitbucket.org/ww/goautoneg",
"model"
]
revision = "e3fb1a1acd7605367a2b378bc2e2f893c05174b7"
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs"
]
revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa"
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
[[projects]]
name = "github.com/spf13/afero"
@ -344,14 +357,14 @@
".",
"mem"
]
revision = "8d919cbe7e2627e417f3e45c3c0e489a5b7e2536"
version = "v1.0.0"
revision = "63644898a8da0bc22138abf860edaf5277b6102e"
version = "v1.1.0"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
name = "github.com/zakjan/cert-chain-resolver"
@ -367,7 +380,7 @@
"ed25519/internal/edwards25519",
"ssh/terminal"
]
revision = "6a293f2d4b14b8e6d3f0539e383f6d0d30fce3fd"
revision = "a3beeb748656e13e54256fd2cde19e058f41f60f"
[[projects]]
branch = "master"
@ -378,15 +391,15 @@
"html",
"html/atom",
"html/charset",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"lex/httplex",
"publicsuffix",
"trace"
]
revision = "a337091b0525af65de94df2eb7e98bd9962dcbe2"
revision = "dfa909b99c79129e1100513e5cd36307665e5723"
[[projects]]
branch = "master"
@ -398,7 +411,7 @@
"jws",
"jwt"
]
revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
revision = "770e5ebd4ab27c4d70b0983674d138217f5e2c72"
[[projects]]
branch = "master"
@ -407,10 +420,9 @@
"unix",
"windows"
]
revision = "1e2299c37cc91a509f1b12369872d27be0ce98a6"
revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = [
"collate",
@ -440,13 +452,14 @@
"unicode/norm",
"unicode/rangetable"
]
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "golang.org/x/time"
packages = ["rate"]
revision = "26559e0f760e39c24d730d3224364aef164ee23f"
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
[[projects]]
name = "google.golang.org/appengine"
@ -469,16 +482,21 @@
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
revision = "11c7f9e547da6db876260ce49ea7536985904c9b"
revision = "694d95ba50e67b2e363f3483057db5d4910c18f9"
[[projects]]
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"channelz",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"internal",
@ -487,13 +505,15 @@
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
"transport"
]
revision = "5ffe3083946d5603a0578721101dc8165b1d5b5f"
version = "v1.7.2"
revision = "41344da2231b913fa3d983840a57a6b1b7b631a1"
version = "v1.12.0"
[[projects]]
name = "gopkg.in/fsnotify/fsnotify.v1"
@ -510,8 +530,8 @@
[[projects]]
name = "gopkg.in/inf.v0"
packages = ["."]
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
version = "v0.9.0"
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
name = "gopkg.in/square/go-jose.v2"
@ -525,10 +545,10 @@
version = "v2.1.6"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
name = "k8s.io/api"
@ -562,7 +582,7 @@
"storage/v1alpha1",
"storage/v1beta1"
]
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[projects]]
name = "k8s.io/apiextensions-apiserver"
@ -574,7 +594,7 @@
"pkg/client/clientset/clientset/typed/apiextensions/v1beta1",
"pkg/features"
]
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[projects]]
name = "k8s.io/apimachinery"
@ -632,7 +652,7 @@
"third_party/forked/golang/netutil",
"third_party/forked/golang/reflect"
]
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[projects]]
name = "k8s.io/apiserver"
@ -645,7 +665,7 @@
"pkg/util/feature",
"pkg/util/logs"
]
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[projects]]
name = "k8s.io/client-go"
@ -814,13 +834,13 @@
"util/retry",
"util/workqueue"
]
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[projects]]
branch = "master"
name = "k8s.io/kube-openapi"
packages = ["pkg/util/proto"]
revision = "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1"
revision = "41e43949ca69d04b66104069ed3ffd619b289b3d"
[[projects]]
name = "k8s.io/kubernetes"
@ -869,17 +889,17 @@
"pkg/volume/util/recyclerclient",
"third_party/forked/golang/expansion"
]
revision = "v1.10.1"
revision = "v1.10.3"
[[projects]]
branch = "master"
name = "k8s.io/utils"
packages = ["exec"]
revision = "bf963466fd3fea33c428098b12a89d8ecd012f25"
revision = "258e2a2fa64568210fbd6267cf1d8fd87c3cb86e"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "62df75064eae4ba4c59404c14d995f4d54774da2675b33295f8a14088886fa82"
inputs-digest = "5feeef324f0cbac72e0234d5f649fc7c4233f4e2bb4477e454e047b5461d7569"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -25,6 +25,12 @@
go-tests = true
unused-packages = true
# Don't prune helper scripts from code-generator
[[prune.project]]
name = "k8s.io/code-generator"
unused-packages = false
non-go = false
[[override]]
name = "github.com/docker/distribution"
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
@ -92,24 +98,24 @@
[[constraint]]
name = "k8s.io/kubernetes"
revision = "v1.10.1"
revision = "v1.10.3"
[[constraint]]
name = "k8s.io/api"
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[constraint]]
name = "k8s.io/apimachinery"
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[constraint]]
name = "k8s.io/client-go"
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"
[[constraint]]
name = "k8s.io/apiserver"
revision = "kubernetes-1.10.1"
revision = "kubernetes-1.10.3"

View file

@ -24,7 +24,13 @@ GOOS?=linux
DOCKER?=docker
SED_I?=sed -i
GOHOSTOS ?= $(shell go env GOHOSTOS)
# e2e settings
# Allow limiting the scope of the e2e tests. By default run everything
FOCUS?=.*
# number of parallel test
E2E_NODES?=3
ifeq ($(GOHOSTOS),darwin)
SED_I=sed -i ''
@ -109,8 +115,6 @@ ifeq ($(ARCH),amd64)
$(SED_I) "/CROSS_BUILD_/d" $(DOCKERFILE)
else
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMUVERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(TEMP_DIR)/rootfs
$(SED_I) "s/CROSS_BUILD_//g" $(DOCKERFILE)
endif
@ -122,6 +126,11 @@ ifeq ($(ARCH), amd64)
$(DOCKER) tag $(MULTI_ARCH_IMG):$(TAG) $(IMAGE):$(TAG)
endif
.PHONY: register-qemu
register-qemu:
# Register /usr/bin/qemu-ARCH-static as the handler for binaries in multiple platforms
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
.PHONY: push
push: .push-$(ARCH)
@ -158,7 +167,15 @@ lua-test:
e2e-test:
@ginkgo version || go get -u github.com/onsi/ginkgo/ginkgo
@ginkgo build ./test/e2e
@KUBECONFIG=${HOME}/.kube/config ginkgo -randomizeSuites -randomizeAllSpecs -flakeAttempts=2 --focus=$(FOCUS) -p -trace -nodes=2 ./test/e2e/e2e.test
@KUBECONFIG=${HOME}/.kube/config ginkgo \
-randomizeSuites \
-randomizeAllSpecs \
-flakeAttempts=2 \
--focus=$(FOCUS) \
-p \
-trace \
-nodes=$(E2E_NODES) \
./test/e2e/e2e.test
.PHONY: cover
cover:
@ -202,6 +219,7 @@ dep-ensure:
dep version || go get -u github.com/golang/dep/cmd/dep
dep ensure -v
dep prune -v
find vendor -name '*_test.go' -delete
.PHONY: dev-env
dev-env:

View file

@ -76,8 +76,6 @@ ifeq ($(ARCH),amd64)
cd $(TEMP_DIR) && $(SED_I) "/CROSS_BUILD_/d" Dockerfile
else
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMUVERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(TEMP_DIR)
cd $(TEMP_DIR) && $(SED_I) "s/CROSS_BUILD_//g" Dockerfile
endif
@ -89,6 +87,11 @@ ifeq ($(ARCH), amd64)
$(DOCKER) tag $(MULTI_ARCH_IMG):$(TAG) $(IMAGE):$(TAG)
endif
.PHONY: register-qemu
register-qemu:
# Register /usr/bin/qemu-ARCH-static as the handler for binaries in multiple platforms
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
push: .push-$(ARCH)
.push-$(ARCH): .container-$(ARCH)
$(DOCKER) push $(MULTI_ARCH_IMG):$(TAG)

View file

@ -20,6 +20,7 @@ import (
"fmt"
"net/http"
"os/exec"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -58,6 +59,7 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
resp, body, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
End()
@ -90,6 +92,7 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
resp, body, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
End()
@ -127,6 +130,7 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
resp, body, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
End()
@ -164,6 +168,7 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
resp, body, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
SetBasicAuth("user", "pass").
End()
@ -202,6 +207,7 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
resp, _, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
SetBasicAuth("foo", "bar").
End()
@ -251,6 +257,7 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
resp, _, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
SetBasicAuth("foo", "bar").
End()

View file

@ -31,6 +31,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/ingress-nginx/test/e2e/framework"
)
@ -77,12 +78,20 @@ var _ = framework.IngressNginxDescribe("Annotations - influxdb", func() {
Expect(res.StatusCode).Should(Equal(http.StatusOK))
time.Sleep(5 * time.Second)
measurements, err := extractInfluxDBMeasurements(f)
var measurements string
err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
measurements, err = extractInfluxDBMeasurements(f)
if err != nil {
return false, nil
}
return true, nil
})
Expect(err).NotTo(HaveOccurred())
var results map[string][]map[string]interface{}
json.Unmarshal([]byte(measurements), &results)
Expect(err).NotTo(HaveOccurred())
Expect(len(measurements)).ShouldNot(Equal(0))
for _, elem := range results["results"] {
Expect(len(elem)).ShouldNot(Equal(0))

View file

@ -181,7 +181,7 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
name: nginx-ingress-role-${NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
@ -196,7 +196,7 @@ subjects:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
name: nginx-ingress-clusterrole-${NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
@ -205,6 +205,7 @@ subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ${NAMESPACE}
---
apiVersion: extensions/v1beta1

View file

@ -1,10 +1,10 @@
sudo: false
language: go
go:
- 1.6
- 1.7
- 1.8
- 1.9
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
install:
- go get -v cloud.google.com/go/...
script:
@ -14,6 +14,7 @@ script:
GCLOUD_TESTS_GOLANG_KEY="$(pwd)/dulcet-port-762-key.json"
GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID="gcloud-golang-firestore-tests"
GCLOUD_TESTS_GOLANG_FIRESTORE_KEY="$(pwd)/gcloud-golang-firestore-tests-key.json"
GCLOUD_TESTS_GOLANG_KEYRING="projects/dulcet-port-762/locations/us/keyRings/go-integration-test"
./run-tests.sh $TRAVIS_COMMIT
env:
matrix:

View file

@ -43,13 +43,19 @@ run the against the actual APIs.
- **GCLOUD_TESTS_GOLANG_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455)
- **GCLOUD_TESTS_GOLANG_KEY**: The path to the JSON key file.
- **GCLOUD_TESTS_API_KEY**: Your API key.
Firestore requires a different project and key:
Some packages require additional environment variables to be set:
- **GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID**: Developers Console project's ID
supporting Firestore
- **GCLOUD_TESTS_GOLANG_FIRESTORE_KEY**: The path to the JSON key file.
- firestore
- **GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID**: project ID for Firestore.
- **GCLOUD_TESTS_GOLANG_FIRESTORE_KEY**: The path to the JSON key file.
- storage
- **GCLOUD_TESTS_GOLANG_KEYRING**: The full name of the keyring for the tests, in the
form "projects/P/locations/L/keyRings/R".
- translate
- **GCLOUD_TESTS_API_KEY**: API key for using the Translate API.
- profiler
- **GCLOUD_TESTS_GOLANG_ZONE**: Compute Engine zone.
Install the [gcloud command-line tool][gcloudcli] to your machine and use it
to create some resources used in integration tests.
@ -80,11 +86,20 @@ $ gcloud beta spanner instances create go-integration-test --config regional-us-
# NOTE: Spanner instances are priced by the node-hour, so you may want to delete
# the instance after testing with 'gcloud beta spanner instances delete'.
# For Storage integration tests:
# Enable KMS for your project in the Cloud Console.
# Create a KMS keyring, in the same location as the default location for your project's buckets.
$ gcloud kms keyrings create MY_KEYRING --location MY_LOCATION
# Create two keys in the keyring, named key1 and key2.
$ gcloud kms keys create key1 --keyring MY_KEYRING --location MY_LOCATION --purpose encryption
$ gcloud kms keys create key2 --keyring MY_KEYRING --location MY_LOCATION --purpose encryption
# As mentioned above, set the GCLOUD_TESTS_GOLANG_KEYRING environment variable.
$ export GCLOUD_TESTS_GOLANG_KEYRING=projects/$GCLOUD_TESTS_GOLANG_PROJECT_ID/locations/MY_LOCATION/keyRings/MY_KEYRING
# Authorize Google Cloud Storage to encrypt and decrypt using key1.
gsutil kms authorize -p $GCLOUD_TESTS_GOLANG_PROJECT_ID -k $GCLOUD_TESTS_GOLANG_KEYRING/cryptoKeys/key1
```
Once you've set the environment variables, you can run the integration tests by
running:
Once you've done the necessary setup, you can run the integration tests by running:
``` sh
$ go test -v cloud.google.com/go/...

View file

@ -22,10 +22,13 @@ David Symonds <dsymonds@golang.org>
Filippo Valsorda <hi@filippo.io>
Glenn Lewis <gmlewis@google.com>
Ingo Oeser <nightlyone@googlemail.com>
James Hall <james.hall@shopify.com>
Johan Euphrosine <proppy@google.com>
Jonathan Amsterdam <jba@google.com>
Kunpei Sakai <namusyaka@gmail.com>
Luna Duclos <luna.duclos@palmstonegames.com>
Magnus Hiie <magnus.hiie@gmail.com>
Mario Castro <mariocaster@gmail.com>
Michael McGreevy <mcgreevy@golang.org>
Omar Jarjur <ojarjur@google.com>
Paweł Knap <pawelknap88@gmail.com>

2
vendor/cloud.google.com/go/LICENSE generated vendored
View file

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014 Google Inc.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

295
vendor/cloud.google.com/go/README.md generated vendored
View file

@ -33,114 +33,221 @@ make backwards-incompatible changes.
## News
_October 30, 2017_
_May 18, 2018_
*v0.16.0*
*v0.23.0*
- Other bigquery changes:
- `JobIterator.Next` returns `*Job`; removed `JobInfo` (BREAKING CHANGE).
- UseStandardSQL is deprecated; set UseLegacySQL to true if you need
Legacy SQL.
- Uploader.Put will generate a random insert ID if you do not provide one.
- Support time partitioning for load jobs.
- Support dry-run queries.
- A `Job` remembers its last retrieved status.
- Support retrieving job configuration.
- Support labels for jobs and tables.
- Support dataset access lists.
- Improve support for external data sources, including data from Bigtable and
Google Sheets, and tables with external data.
- Support updating a table's view configuration.
- Fix uploading civil times with nanoseconds.
- bigquery: Add DDL stats to query statistics.
- bigtable:
- cbt: Add cells-per-column limit for row lookup.
- cbt: Make it possible to combine read filters.
- dlp: v2beta2 client removed. Use the v2 client instead.
- firestore, spanner: Fix compilation errors due to protobuf changes.
- storage:
- Support PubSub notifications.
- Support Requester Pays buckets.
_May 8, 2018_
- profiler: Support goroutine and mutex profile types.
*v0.22.0*
- bigtable:
- cbt: Support cells per column limit for row read.
- bttest: Correctly handle empty RowSet.
- Fix ReadModifyWrite operation in emulator.
- Fix API path in GetCluster.
_October 3, 2017_
- bigquery:
- BEHAVIOR CHANGE: Retry on 503 status code.
- Add dataset.DeleteWithContents.
- Add SchemaUpdateOptions for query jobs.
- Add Timeline to QueryStatistics.
- Add more stats to ExplainQueryStage.
- Support Parquet data format.
*v0.15.0*
- datastore:
- Support omitempty for times.
- firestore: beta release. See the
[announcement](https://firebase.googleblog.com/2017/10/introducing-cloud-firestore.html).
- dlp:
- **BREAKING CHANGE:** Remove v1beta1 client. Please migrate to the v2 client,
which is now out of beta.
- Add v2 client.
- errorreporting: The existing package has been redesigned.
- firestore:
- BEHAVIOR CHANGE: Treat set({}, MergeAll) as valid.
- errors: This package has been removed. Use errorreporting.
_September 28, 2017_
*v0.14.0*
- bigquery BREAKING CHANGES:
- Standard SQL is the default for queries and views.
- `Table.Create` takes `TableMetadata` as a second argument, instead of
options.
- `Dataset.Create` takes `DatasetMetadata` as a second argument.
- `DatasetMetadata` field `ID` renamed to `FullID`
- `TableMetadata` field `ID` renamed to `FullID`
- Other bigquery changes:
- The client will append a random suffix to a provided job ID if you set
`AddJobIDSuffix` to true in a job config.
- Listing jobs is supported.
- Better retry logic.
- vision, language, speech: clients are now stable
- monitoring: client is now beta
- iam:
- Support JWT signing via SignJwt callopt.
- profiler:
- Rename InstanceName to Instance, ZoneName to Zone
- Auto-detect service name and version on AppEngine.
- BEHAVIOR CHANGE: PollForSerialOutput returns an error when context.Done.
- BEHAVIOR CHANGE: Increase the initial backoff to 1 minute.
- Avoid returning empty serial port output.
_September 8, 2017_
- pubsub:
- BEHAVIOR CHANGE: Don't backoff during next retryable error once stream is healthy.
- BEHAVIOR CHANGE: Don't backoff on EOF.
- pstest: Support Acknowledge and ModifyAckDeadline RPCs.
*v0.13.0*
- redis:
- Add v1 beta Redis client.
- bigquery: UseLegacySQL options for CreateTable and QueryConfig. Use these
options to continue using Legacy SQL after the client switches its default
to Standard SQL.
- spanner:
- Support SessionLabels.
- bigquery: Support for updating dataset labels.
- speech:
- Add api v1 beta1 client.
- bigquery: Set DatasetIterator.ProjectID to list datasets in a project other
than the client's. DatasetsInProject is no longer needed and is deprecated.
- storage:
- BEHAVIOR CHANGE: Retry reads when retryable error occurs.
- Fix delete of object in requester-pays bucket.
- Support KMS integration.
- bigtable: Fail ListInstances when any zones fail.
_April 9, 2018_
- spanner: support decoding of slices of basic types (e.g. []string, []int64,
etc.)
*v0.21.0*
- logging/logadmin: UpdateSink no longer creates a sink if it is missing
(actually a change to the underlying service, not the client)
- bigquery:
- Add OpenCensus tracing.
- profiler: Service and ServiceVersion replace Target in Config.
- firestore:
- **BREAKING CHANGE:** If a document does not exist, return a DocumentSnapshot
whose Exists method returns false. DocumentRef.Get and Transaction.Get
return the non-nil DocumentSnapshot in addition to a NotFound error.
**DocumentRef.GetAll and Transaction.GetAll return a non-nil
DocumentSnapshot instead of nil.**
- Add DocumentIterator.Stop. **Call Stop whenever you are done with a
DocumentIterator.**
- Added Query.Snapshots and DocumentRef.Snapshots, which provide realtime
notification of updates. See https://cloud.google.com/firestore/docs/query-data/listen.
- Canceling an RPC now always returns a grpc.Status with codes.Canceled.
_August 22, 2017_
- spanner:
- Add `CommitTimestamp`, which supports inserting the commit timestamp of a
transaction into a column.
*v0.12.0*
_March 22, 2018_
- pubsub: Subscription.Receive now uses streaming pull.
*v0.20.0*
- pubsub: add Client.TopicInProject to access topics in a different project
than the client.
- bigquery: Support SchemaUpdateOptions for load jobs.
- errors: renamed errorreporting. The errors package will be removed shortly.
- bigtable:
- Add SampleRowKeys.
- cbt: Support union, intersection GCPolicy.
- Retry admin RPCS.
- Add trace spans to retries.
- datastore: improved retry behavior.
- datastore: Add OpenCensus tracing.
- bigquery: support updates to dataset metadata, with etags.
- firestore:
- Fix queries involving Null and NaN.
- Allow Timestamp protobuffers for time values.
- bigquery: add etag support to Table.Update (BREAKING: etag argument added).
- logging: Add a WriteTimeout option.
- bigquery: generate all job IDs on the client.
- spanner: Support Batch API.
- storage: support bucket lifecycle configurations.
- storage: Add OpenCensus tracing.
_February 26, 2018_
*v0.19.0*
- bigquery:
- Support customer-managed encryption keys.
- bigtable:
- Improved emulator support.
- Support GetCluster.
- datastore:
- Add general mutations.
- Support pointer struct fields.
- Support transaction options.
- firestore:
- Add Transaction.GetAll.
- Support document cursors.
- logging:
- Support concurrent RPCs to the service.
- Support per-entry resources.
- profiler:
- Add config options to disable heap and thread profiling.
- Read the project ID from $GOOGLE_CLOUD_PROJECT when it's set.
- pubsub:
- BEHAVIOR CHANGE: Release flow control after ack/nack (instead of after the
callback returns).
- Add SubscriptionInProject.
- Add OpenCensus instrumentation for streaming pull.
- storage:
- Support CORS.
_January 18, 2018_
*v0.18.0*
- bigquery:
- Marked stable.
- Schema inference of nullable fields supported.
- Added TimePartitioning to QueryConfig.
- firestore: Data provided to DocumentRef.Set with a Merge option can contain
Delete sentinels.
- logging: Clients can accept parent resources other than projects.
- pubsub:
- pubsub/pstest: A lighweight fake for pubsub. Experimental; feedback welcome.
- Support updating more subscription metadata: AckDeadline,
RetainAckedMessages and RetentionDuration.
- oslogin/apiv1beta: New client for the Cloud OS Login API.
- rpcreplay: A package for recording and replaying gRPC traffic.
- spanner:
- Add a ReadWithOptions that supports a row limit, as well as an index.
- Support query plan and execution statistics.
- Added [OpenCensus](http://opencensus.io) support.
- storage: Clarify checksum validation for gzipped files (it is not validated
when the file is served uncompressed).
_December 11, 2017_
*v0.17.0*
- firestore BREAKING CHANGES:
- Remove UpdateMap and UpdateStruct; rename UpdatePaths to Update.
Change
`docref.UpdateMap(ctx, map[string]interface{}{"a.b", 1})`
to
`docref.Update(ctx, []firestore.Update{{Path: "a.b", Value: 1}})`
Change
`docref.UpdateStruct(ctx, []string{"Field"}, aStruct)`
to
`docref.Update(ctx, []firestore.Update{{Path: "Field", Value: aStruct.Field}})`
- Rename MergePaths to Merge; require args to be FieldPaths
- A value stored as an integer can be read into a floating-point field, and vice versa.
- bigtable/cmd/cbt:
- Support deleting a column.
- Add regex option for row read.
- spanner: Mark stable.
- storage:
- Add Reader.ContentEncoding method.
- Fix handling of SignedURL headers.
- bigquery:
- If Uploader.Put is called with no rows, it returns nil without making a
call.
- Schema inference supports the "nullable" option in struct tags for
non-required fields.
- TimePartitioning supports "Field".
[Older news](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/old-news.md)
@ -149,22 +256,25 @@ _August 22, 2017_
Google API | Status | Package
---------------------------------|--------------|-----------------------------------------------------------
[BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
[Bigtable][cloud-bigtable] | stable | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[Container][cloud-container] | alpha | [`cloud.google.com/go/container/apiv1`][cloud-container-ref]
[Data Loss Prevention][cloud-dlp]| alpha | [`cloud.google.com/go/dlp/apiv2beta1`][cloud-dlp-ref]
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
[Debugger][cloud-debugger] | alpha | [`cloud.google.com/go/debugger/apiv2`][cloud-debugger-ref]
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`][cloud-errors-ref]
[Firestore][cloud-firestore] | beta | [`cloud.google.com/go/firestore`][cloud-firestore-ref]
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref]
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[BigQuery][cloud-bigquery] | beta | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
[Language][cloud-language] | stable | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref]
[Monitoring][cloud-monitoring] | beta | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref]
[OS Login][cloud-oslogin] | alpha | [`cloud.google.com/compute/docs/oslogin/rest`][cloud-oslogin-ref]
[Pub/Sub][cloud-pubsub] | beta | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref]
[Language][cloud-language] | stable | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref]
[Spanner][cloud-spanner] | beta | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref]
[Translation][cloud-translation] | stable | [`cloud.google.com/go/translate`][cloud-translation-ref]
[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace`][cloud-trace-ref]
[Video Intelligence][cloud-video]| beta | [`cloud.google.com/go/videointelligence/apiv1beta1`][cloud-video-ref]
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`][cloud-errors-ref]
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref]
> **Alpha status**: the API is still being actively developed. As a
@ -519,6 +629,9 @@ for more information.
[cloud-language]: https://cloud.google.com/natural-language
[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1
[cloud-oslogin]: https://cloud.google.com/compute/docs/oslogin/rest
[cloud-oslogin-ref]: https://cloud.google.com/compute/docs/oslogin/rest
[cloud-speech]: https://cloud.google.com/speech
[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1
@ -529,13 +642,19 @@ for more information.
[cloud-translation]: https://cloud.google.com/translation
[cloud-translation-ref]: https://godoc.org/cloud.google.com/go/translation
[cloud-trace]: https://cloud.google.com/trace/
[cloud-trace-ref]: https://godoc.org/cloud.google.com/go/trace
[cloud-video]: https://cloud.google.com/video-intelligence/
[cloud-video-ref]: https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1
[cloud-errors]: https://cloud.google.com/error-reporting/
[cloud-errors-ref]: https://godoc.org/cloud.google.com/go/errorreporting
[cloud-container]: https://cloud.google.com/containers/
[cloud-container-ref]: https://godoc.org/cloud.google.com/go/container/apiv1
[cloud-debugger]: https://cloud.google.com/debugger/
[cloud-debugger-ref]: https://godoc.org/cloud.google.com/go/debugger/apiv2
[cloud-dlp]: https://cloud.google.com/dlp/
[cloud-dlp-ref]: https://godoc.org/cloud.google.com/go/dlp/apiv2beta1
[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials

13
vendor/cloud.google.com/go/RELEASING.md generated vendored Normal file
View file

@ -0,0 +1,13 @@
# How to Release this Repo
1. Determine the current release version with `git tag -l`. It should look
something like `vX.Y.Z`. We'll call the current
version `$CV` and the new version `$NV`.
1. On master, run `git log $CV..` to list all the changes since the last
release.
1. Edit the News section of `README.md` to include a summary of the changes.
1. Mail the CL containing the `README.md` changes. When the CL is approved, submit it.
1. Without submitting any other CLs:
a. Switch to master.
b. Tag the repo with the next version: `git tag $NV`.
c. Push the tag: `git push origin $NV`.

View file

@ -1,49 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 cloud_test
import (
"cloud.google.com/go/datastore"
"golang.org/x/net/context"
"google.golang.org/api/option"
)
func Example_applicationDefaultCredentials() {
// Google Application Default Credentials is the recommended way to authorize
// and authenticate clients.
//
// See the following link on how to create and obtain Application Default Credentials:
// https://developers.google.com/identity/protocols/application-default-credentials.
client, err := datastore.NewClient(context.Background(), "project-id")
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}
func Example_serviceAccountFile() {
// Use a JSON key file associated with a Google service account to
// authenticate and authorize. Service Account keys can be created and
// downloaded from https://console.developers.google.com/permissions/serviceaccounts.
//
// Note: This example uses the datastore client, but the same steps apply to
// the other client libraries underneath this package.
client, err := datastore.NewClient(context.Background(),
"project-id", option.WithServiceAccountFile("/path/to/service-account-key.json"))
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}

61
vendor/cloud.google.com/go/cloud.go generated vendored
View file

@ -12,9 +12,60 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cloud is the root of the packages used to access Google Cloud
// Services. See https://godoc.org/cloud.google.com/go for a full list
// of sub-packages.
//
// This package documents how to authorize and authenticate the sub packages.
/*
Package cloud is the root of the packages used to access Google Cloud
Services. See https://godoc.org/cloud.google.com/go for a full list
of sub-packages.
Authentication and Authorization
All the clients in sub-packages support authentication via Google Application Default
Credentials (see https://cloud.google.com/docs/authentication/production), or
by providing a JSON key file for a Service Account. See the authentication examples
in this package for details.
Timeouts and Cancellation
By default, all requests in sub-packages will run indefinitely, retrying on transient
errors when correctness allows. To set timeouts or arrange for cancellation, use
contexts. See the examples for details.
Do not attempt to control the initial connection (dialing) of a service by setting a
timeout on the context passed to NewClient. Dialing is non-blocking, so timeouts
would be ineffective and would only interfere with credential refreshing, which uses
the same context.
Connection Pooling
Connection pooling differs in clients based on their transport. Cloud
clients either rely on HTTP or gRPC transports to communicate
with Google Cloud.
Cloud clients that use HTTP (bigquery, compute, storage, and translate) rely on the
underlying HTTP transport to cache connections for later re-use. These are cached to
the default http.MaxIdleConns and http.MaxIdleConnsPerHost settings in
http.DefaultTransport.
For gPRC clients (all others in this repo), connection pooling is configurable. Users
of cloud client libraries may specify option.WithGRPCConnectionPool(n) as a client
option to NewClient calls. This configures the underlying gRPC connections to be
pooled and addressed in a round robin fashion.
Using the Libraries with Docker
Minimal docker images like Alpine lack CA certificates. This causes RPCs to appear to
hang, because gRPC retries indefinitely. See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/928
for more information.
Debugging
To see gRPC logs, set the environment variable GRPC_GO_LOG_SEVERITY_LEVEL. See
https://godoc.org/google.golang.org/grpc/grpclog for more information.
For HTTP logging, set the GODEBUG environment variable to "http2debug=1" or "http2debug=2".
*/
package cloud // import "cloud.google.com/go"

View file

@ -1,48 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 metadata
import (
"os"
"sync"
"testing"
)
func TestOnGCE_Stress(t *testing.T) {
if testing.Short() {
t.Skip("skipping in -short mode")
}
var last bool
for i := 0; i < 100; i++ {
onGCEOnce = sync.Once{}
now := OnGCE()
if i > 0 && now != last {
t.Errorf("%d. changed from %v to %v", i, last, now)
}
last = now
}
t.Logf("OnGCE() = %v", last)
}
func TestOnGCE_Force(t *testing.T) {
onGCEOnce = sync.Once{}
old := os.Getenv(metadataHostEnv)
defer os.Setenv(metadataHostEnv, old)
os.Setenv(metadataHostEnv, "127.0.0.1")
if !OnGCE() {
t.Error("OnGCE() = false; want true")
}
}

17
vendor/cloud.google.com/go/issue_template.md generated vendored Normal file
View file

@ -0,0 +1,17 @@
(delete this for feature requests)
## Client
e.g. PubSub
## Describe Your Environment
e.g. Alpine Docker on GKE
## Expected Behavior
e.g. Messages arrive really fast.
## Actual Behavior
e.g. Messages arrive really slowly.

View file

@ -1,70 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 cloud
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
var sentinels = []string{
"Copyright",
"Google Inc",
`Licensed under the Apache License, Version 2.0 (the "License");`,
}
func TestLicense(t *testing.T) {
err := filepath.Walk(".", func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if ext := filepath.Ext(path); ext != ".go" && ext != ".proto" {
return nil
}
if strings.HasSuffix(path, ".pb.go") {
// .pb.go files are generated from the proto files.
// .proto files must have license headers.
return nil
}
if path == "bigtable/cmd/cbt/cbtdoc.go" {
// Automatically generated.
return nil
}
src, err := ioutil.ReadFile(path)
if err != nil {
return nil
}
src = src[:140] // Ensure all of the sentinel values are at the top of the file.
// Find license
for _, sentinel := range sentinels {
if !bytes.Contains(src, []byte(sentinel)) {
t.Errorf("%v: license header not present. want %q", path, sentinel)
return nil
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}

View file

@ -1,3 +1,113 @@
_October 30, 2017_
*v0.16.0*
- Other bigquery changes:
- `JobIterator.Next` returns `*Job`; removed `JobInfo` (BREAKING CHANGE).
- UseStandardSQL is deprecated; set UseLegacySQL to true if you need
Legacy SQL.
- Uploader.Put will generate a random insert ID if you do not provide one.
- Support time partitioning for load jobs.
- Support dry-run queries.
- A `Job` remembers its last retrieved status.
- Support retrieving job configuration.
- Support labels for jobs and tables.
- Support dataset access lists.
- Improve support for external data sources, including data from Bigtable and
Google Sheets, and tables with external data.
- Support updating a table's view configuration.
- Fix uploading civil times with nanoseconds.
- storage:
- Support PubSub notifications.
- Support Requester Pays buckets.
- profiler: Support goroutine and mutex profile types.
_October 3, 2017_
*v0.15.0*
- firestore: beta release. See the
[announcement](https://firebase.googleblog.com/2017/10/introducing-cloud-firestore.html).
- errorreporting: The existing package has been redesigned.
- errors: This package has been removed. Use errorreporting.
_September 28, 2017_
*v0.14.0*
- bigquery BREAKING CHANGES:
- Standard SQL is the default for queries and views.
- `Table.Create` takes `TableMetadata` as a second argument, instead of
options.
- `Dataset.Create` takes `DatasetMetadata` as a second argument.
- `DatasetMetadata` field `ID` renamed to `FullID`
- `TableMetadata` field `ID` renamed to `FullID`
- Other bigquery changes:
- The client will append a random suffix to a provided job ID if you set
`AddJobIDSuffix` to true in a job config.
- Listing jobs is supported.
- Better retry logic.
- vision, language, speech: clients are now stable
- monitoring: client is now beta
- profiler:
- Rename InstanceName to Instance, ZoneName to Zone
- Auto-detect service name and version on AppEngine.
_September 8, 2017_
*v0.13.0*
- bigquery: UseLegacySQL options for CreateTable and QueryConfig. Use these
options to continue using Legacy SQL after the client switches its default
to Standard SQL.
- bigquery: Support for updating dataset labels.
- bigquery: Set DatasetIterator.ProjectID to list datasets in a project other
than the client's. DatasetsInProject is no longer needed and is deprecated.
- bigtable: Fail ListInstances when any zones fail.
- spanner: support decoding of slices of basic types (e.g. []string, []int64,
etc.)
- logging/logadmin: UpdateSink no longer creates a sink if it is missing
(actually a change to the underlying service, not the client)
- profiler: Service and ServiceVersion replace Target in Config.
_August 22, 2017_
*v0.12.0*
- pubsub: Subscription.Receive now uses streaming pull.
- pubsub: add Client.TopicInProject to access topics in a different project
than the client.
- errors: renamed errorreporting. The errors package will be removed shortly.
- datastore: improved retry behavior.
- bigquery: support updates to dataset metadata, with etags.
- bigquery: add etag support to Table.Update (BREAKING: etag argument added).
- bigquery: generate all job IDs on the client.
- storage: support bucket lifecycle configurations.
_July 31, 2017_
*v0.11.0*

68
vendor/cloud.google.com/go/regen-gapic.sh generated vendored Executable file
View file

@ -0,0 +1,68 @@
#!/bin/bash
# This script generates all GAPIC clients in this repo.
# One-time setup:
# cd path/to/googleapis # https://github.com/googleapis/googleapis
# virtualenv env
# . env/bin/activate
# pip install googleapis-artman
# deactivate
#
# Regenerate:
# cd path/to/googleapis
# . env/bin/activate
# $GOPATH/src/cloud.google.com/go/regen-gapic.sh
# deactivate
#
# Being in googleapis directory is important;
# that's where we find YAML files and where artman puts the "artman-genfiles" directory.
#
# NOTE: This script does not generate the "raw" gRPC client found in google.golang.org/genproto.
# To do that, use the regen.sh script in the genproto repo instead.
set -ex
APIS=(
google/iam/artman_iam_admin.yaml
google/cloud/bigquery/datatransfer/artman_bigquerydatatransfer.yaml
google/cloud/dataproc/artman_dataproc_v1.yaml
google/cloud/dialogflow/artman_dialogflow_v2.yaml
google/cloud/language/artman_language_v1.yaml
google/cloud/language/artman_language_v1beta2.yaml
google/cloud/oslogin/artman_oslogin_v1beta.yaml
google/cloud/redis/artman_redis_v1beta1.yaml
google/cloud/speech/artman_speech_v1.yaml
google/cloud/speech/artman_speech_v1beta1.yaml
google/cloud/speech/artman_speech_v1p1beta1.yaml
google/cloud/tasks/artman_cloudtasks.yaml
google/cloud/videointelligence/artman_videointelligence_v1beta1.yaml
google/cloud/videointelligence/artman_videointelligence_v1beta2.yaml
google/cloud/vision/artman_vision_v1.yaml
google/cloud/vision/artman_vision_v1p1beta1.yaml
google/container/artman_container.yaml
google/devtools/artman_clouddebugger.yaml
google/devtools/clouderrorreporting/artman_errorreporting.yaml
google/devtools/cloudtrace/artman_cloudtrace_v1.yaml
google/devtools/cloudtrace/artman_cloudtrace_v2.yaml
google/firestore/artman_firestore.yaml
google/logging/artman_logging.yaml
google/longrunning/artman_longrunning.yaml
google/monitoring/artman_monitoring.yaml
google/privacy/dlp/artman_dlp_v2.yaml
google/pubsub/artman_pubsub.yaml
google/spanner/admin/database/artman_spanner_admin_database.yaml
google/spanner/admin/instance/artman_spanner_admin_instance.yaml
google/spanner/artman_spanner.yaml
)
for api in "${APIS[@]}"; do
rm -rf artman-genfiles/*
artman --config "$api" generate go_gapic
cp -r artman-genfiles/gapi-*/cloud.google.com/go/* $GOPATH/src/cloud.google.com/go/
done
#go list cloud.google.com/go/... | grep apiv | xargs go test
#go test -short cloud.google.com/go/...
#echo "googleapis version: $(git rev-parse HEAD)"

View file

@ -70,8 +70,8 @@ function depends_on_changed_package {
return 1
}
# Collect the packages into two separate lists. (It is faster go test a list of
# packages than to individually go test each one.)
# Collect the packages into two separate lists. (It is faster to call "go test" on a
# list of packages than to individually "go test" each one.)
shorts=
fulls=

View file

@ -1,5 +1,98 @@
# CHANGELOG
## v9.9.0
### New Features
- Added EventGridKeyAuthorizer for key authorization with event grid topics.
### Bug Fixes
- Fixed race condition when auto-refreshing service principal tokens.
## v9.8.1
### Bug Fixes
- Added http.StatusNoContent (204) to the list of expected status codes for long-running operations.
- Updated runtime version info so it's current.
## v9.8.0
### New Features
- Added type azure.AsyncOpIncompleteError to be returned from a future's Result() method when the operation has not completed.
## v9.7.1
### Bug Fixes
- Use correct AAD and Graph endpoints for US Gov environment.
## v9.7.0
### New Features
- Added support for application/octet-stream MIME types.
## v9.6.1
### Bug Fixes
- Ensure Authorization header is added to request when polling for registration status.
## v9.6.0
### New Features
- Added support for acquiring tokens via MSI with a user assigned identity.
## v9.5.3
### Bug Fixes
- Don't remove encoding of existing URL Query parameters when calling autorest.WithQueryParameters.
- Set correct Content Type when using autorest.WithFormData.
## v9.5.2
### Bug Fixes
- Check for nil *http.Response before dereferencing it.
## v9.5.1
### Bug Fixes
- Don't count http.StatusTooManyRequests (429) against the retry cap.
- Use retry logic when SkipResourceProviderRegistration is set to true.
## v9.5.0
### New Features
- Added support for username + password, API key, authoriazation code and cognitive services authentication.
- Added field SkipResourceProviderRegistration to clients to provide a way to skip auto-registration of RPs.
- Added utility function AsStringSlice() to convert its parameters to a string slice.
### Bug Fixes
- When checking for authentication failures look at the error type not the status code as it could vary.
## v9.4.2
### Bug Fixes
- Validate parameters when creating credentials.
- Don't retry requests if the returned status is a 401 (http.StatusUnauthorized) as it will never succeed.
## v9.4.1
### Bug Fixes
- Update the AccessTokensPath() to read access tokens path through AZURE_ACCESS_TOKEN_FILE. If this
environment variable is not set, it will fall back to use default path set by Azure CLI.
- Use case-insensitive string comparison for polling states.
## v9.4.0
### New Features

View file

@ -218,6 +218,40 @@ if (err == nil) {
}
```
#### Username password authenticate
```Go
spt, err := adal.NewServicePrincipalTokenFromUsernamePassword(
oauthConfig,
applicationID,
username,
password,
resource,
callbacks...)
if (err == nil) {
token := spt.Token
}
```
#### Authorization code authenticate
``` Go
spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode(
oauthConfig,
applicationID,
clientSecret,
authorizationCode,
redirectURI,
resource,
callbacks...)
err = spt.Refresh()
if (err == nil) {
token := spt.Token
}
```
### Command Line Tool
A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above.

View file

@ -32,8 +32,24 @@ type OAuthConfig struct {
DeviceCodeEndpoint url.URL
}
// IsZero returns true if the OAuthConfig object is zero-initialized.
func (oac OAuthConfig) IsZero() bool {
return oac == OAuthConfig{}
}
func validateStringParam(param, name string) error {
if len(param) == 0 {
return fmt.Errorf("parameter '" + name + "' cannot be empty")
}
return nil
}
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil {
return nil, err
}
// it's legal for tenantID to be empty so don't validate it
const activeDirectoryEndpointTemplate = "%s/oauth2/%s?api-version=%s"
u, err := url.Parse(activeDirectoryEndpoint)
if err != nil {

View file

@ -1,44 +0,0 @@
package adal
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"testing"
)
func TestNewOAuthConfig(t *testing.T) {
const testActiveDirectoryEndpoint = "https://login.test.com"
const testTenantID = "tenant-id-test"
config, err := NewOAuthConfig(testActiveDirectoryEndpoint, testTenantID)
if err != nil {
t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err)
}
expected := "https://login.test.com/tenant-id-test/oauth2/authorize?api-version=1.0"
if config.AuthorizeEndpoint.String() != expected {
t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint)
}
expected = "https://login.test.com/tenant-id-test/oauth2/token?api-version=1.0"
if config.TokenEndpoint.String() != expected {
t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint)
}
expected = "https://login.test.com/tenant-id-test/oauth2/devicecode?api-version=1.0"
if config.DeviceCodeEndpoint.String() != expected {
t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint)
}
}

View file

@ -1,330 +0,0 @@
package adal
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/Azure/go-autorest/autorest/mocks"
)
const (
TestResource = "SomeResource"
TestClientID = "SomeClientID"
TestTenantID = "SomeTenantID"
TestActiveDirectoryEndpoint = "https://login.test.com/"
)
var (
testOAuthConfig, _ = NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID)
TestOAuthConfig = *testOAuthConfig
)
const MockDeviceCodeResponse = `
{
"device_code": "10000-40-1234567890",
"user_code": "ABCDEF",
"verification_url": "http://aka.ms/deviceauth",
"expires_in": "900",
"interval": "0"
}
`
const MockDeviceTokenResponse = `{
"access_token": "accessToken",
"refresh_token": "refreshToken",
"expires_in": "1000",
"expires_on": "2000",
"not_before": "3000",
"resource": "resource",
"token_type": "type"
}
`
func TestDeviceCodeIncludesResource(t *testing.T) {
sender := mocks.NewSender()
sender.AppendResponse(mocks.NewResponseWithContent(MockDeviceCodeResponse))
code, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource)
if err != nil {
t.Fatalf("adal: unexpected error initiating device auth")
}
if code.Resource != TestResource {
t.Fatalf("adal: InitiateDeviceAuth failed to stash the resource in the DeviceCode struct")
}
}
func TestDeviceCodeReturnsErrorIfSendingFails(t *testing.T) {
sender := mocks.NewSender()
sender.SetError(fmt.Errorf("this is an error"))
_, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource)
if err == nil || !strings.Contains(err.Error(), errCodeSendingFails) {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeSendingFails, err.Error())
}
}
func TestDeviceCodeReturnsErrorIfBadRequest(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody("doesn't matter")
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request"))
_, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource)
if err == nil || !strings.Contains(err.Error(), errCodeHandlingFails) {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeHandlingFails, err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceCodeReturnsErrorIfCannotDeserializeDeviceCode(t *testing.T) {
gibberishJSON := strings.Replace(MockDeviceCodeResponse, "expires_in", "\":, :gibberish", -1)
sender := mocks.NewSender()
body := mocks.NewBody(gibberishJSON)
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK"))
_, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource)
if err == nil || !strings.Contains(err.Error(), errCodeHandlingFails) {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeHandlingFails, err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceCodeReturnsErrorIfEmptyDeviceCode(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody("")
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK"))
_, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource)
if err != ErrDeviceCodeEmpty {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", ErrDeviceCodeEmpty, err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func deviceCode() *DeviceCode {
var deviceCode DeviceCode
_ = json.Unmarshal([]byte(MockDeviceCodeResponse), &deviceCode)
deviceCode.Resource = TestResource
deviceCode.ClientID = TestClientID
return &deviceCode
}
func TestDeviceTokenReturns(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody(MockDeviceTokenResponse)
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err != nil {
t.Fatalf("adal: got error unexpectedly")
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceTokenReturnsErrorIfSendingFails(t *testing.T) {
sender := mocks.NewSender()
sender.SetError(fmt.Errorf("this is an error"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err == nil || !strings.Contains(err.Error(), errTokenSendingFails) {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenSendingFails, err.Error())
}
}
func TestDeviceTokenReturnsErrorIfServerError(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody("")
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusInternalServerError, "Internal Server Error"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err == nil || !strings.Contains(err.Error(), errTokenHandlingFails) {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenHandlingFails, err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceTokenReturnsErrorIfCannotDeserializeDeviceToken(t *testing.T) {
gibberishJSON := strings.Replace(MockDeviceTokenResponse, "expires_in", ";:\"gibberish", -1)
sender := mocks.NewSender()
body := mocks.NewBody(gibberishJSON)
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err == nil || !strings.Contains(err.Error(), errTokenHandlingFails) {
t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenHandlingFails, err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func errorDeviceTokenResponse(message string) string {
return `{ "error": "` + message + `" }`
}
func TestDeviceTokenReturnsErrorIfAuthorizationPending(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody(errorDeviceTokenResponse("authorization_pending"))
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request"))
_, err := CheckForUserCompletion(sender, deviceCode())
if err != ErrDeviceAuthorizationPending {
t.Fatalf("!!!")
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceTokenReturnsErrorIfSlowDown(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody(errorDeviceTokenResponse("slow_down"))
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request"))
_, err := CheckForUserCompletion(sender, deviceCode())
if err != ErrDeviceSlowDown {
t.Fatalf("!!!")
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
type deviceTokenSender struct {
errorString string
attempts int
}
func newDeviceTokenSender(deviceErrorString string) *deviceTokenSender {
return &deviceTokenSender{errorString: deviceErrorString, attempts: 0}
}
func (s *deviceTokenSender) Do(req *http.Request) (*http.Response, error) {
var resp *http.Response
if s.attempts < 1 {
s.attempts++
resp = mocks.NewResponseWithContent(errorDeviceTokenResponse(s.errorString))
} else {
resp = mocks.NewResponseWithContent(MockDeviceTokenResponse)
}
return resp, nil
}
// since the above only exercise CheckForUserCompletion, we repeat the test here,
// but with the intent of showing that WaitForUserCompletion loops properly.
func TestDeviceTokenSucceedsWithIntermediateAuthPending(t *testing.T) {
sender := newDeviceTokenSender("authorization_pending")
_, err := WaitForUserCompletion(sender, deviceCode())
if err != nil {
t.Fatalf("unexpected error occurred")
}
}
// same as above but with SlowDown now
func TestDeviceTokenSucceedsWithIntermediateSlowDown(t *testing.T) {
sender := newDeviceTokenSender("slow_down")
_, err := WaitForUserCompletion(sender, deviceCode())
if err != nil {
t.Fatalf("unexpected error occurred")
}
}
func TestDeviceTokenReturnsErrorIfAccessDenied(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody(errorDeviceTokenResponse("access_denied"))
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err != ErrDeviceAccessDenied {
t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceAccessDenied.Error(), err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceTokenReturnsErrorIfCodeExpired(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody(errorDeviceTokenResponse("code_expired"))
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err != ErrDeviceCodeExpired {
t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceCodeExpired.Error(), err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceTokenReturnsErrorForUnknownError(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody(errorDeviceTokenResponse("unknown_error"))
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err == nil {
t.Fatalf("failed to get error")
}
if err != ErrDeviceGeneric {
t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceGeneric.Error(), err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}
func TestDeviceTokenReturnsErrorIfTokenEmptyAndStatusOK(t *testing.T) {
sender := mocks.NewSender()
body := mocks.NewBody("")
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK"))
_, err := WaitForUserCompletion(sender, deviceCode())
if err != ErrOAuthTokenEmpty {
t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrOAuthTokenEmpty.Error(), err.Error())
}
if body.IsOpen() {
t.Fatalf("response body was left open!")
}
}

View file

@ -1,171 +0,0 @@
package adal
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"io/ioutil"
"os"
"path"
"reflect"
"runtime"
"strings"
"testing"
)
const MockTokenJSON string = `{
"access_token": "accessToken",
"refresh_token": "refreshToken",
"expires_in": "1000",
"expires_on": "2000",
"not_before": "3000",
"resource": "resource",
"token_type": "type"
}`
var TestToken = Token{
AccessToken: "accessToken",
RefreshToken: "refreshToken",
ExpiresIn: "1000",
ExpiresOn: "2000",
NotBefore: "3000",
Resource: "resource",
Type: "type",
}
func writeTestTokenFile(t *testing.T, suffix string, contents string) *os.File {
f, err := ioutil.TempFile(os.TempDir(), suffix)
if err != nil {
t.Fatalf("azure: unexpected error when creating temp file: %v", err)
}
defer f.Close()
_, err = f.Write([]byte(contents))
if err != nil {
t.Fatalf("azure: unexpected error when writing temp test file: %v", err)
}
return f
}
func TestLoadToken(t *testing.T) {
f := writeTestTokenFile(t, "testloadtoken", MockTokenJSON)
defer os.Remove(f.Name())
expectedToken := TestToken
actualToken, err := LoadToken(f.Name())
if err != nil {
t.Fatalf("azure: unexpected error loading token from file: %v", err)
}
if *actualToken != expectedToken {
t.Fatalf("azure: failed to decode properly expected(%v) actual(%v)", expectedToken, *actualToken)
}
// test that LoadToken closes the file properly
err = SaveToken(f.Name(), 0600, *actualToken)
if err != nil {
t.Fatalf("azure: could not save token after LoadToken: %v", err)
}
}
func TestLoadTokenFailsBadPath(t *testing.T) {
_, err := LoadToken("/tmp/this_file_should_never_exist_really")
expectedSubstring := "failed to open file"
if err == nil || !strings.Contains(err.Error(), expectedSubstring) {
t.Fatalf("azure: failed to get correct error expected(%s) actual(%s)", expectedSubstring, err.Error())
}
}
func TestLoadTokenFailsBadJson(t *testing.T) {
gibberishJSON := strings.Replace(MockTokenJSON, "expires_on", ";:\"gibberish", -1)
f := writeTestTokenFile(t, "testloadtokenfailsbadjson", gibberishJSON)
defer os.Remove(f.Name())
_, err := LoadToken(f.Name())
expectedSubstring := "failed to decode contents of file"
if err == nil || !strings.Contains(err.Error(), expectedSubstring) {
t.Fatalf("azure: failed to get correct error expected(%s) actual(%s)", expectedSubstring, err.Error())
}
}
func token() *Token {
var token Token
json.Unmarshal([]byte(MockTokenJSON), &token)
return &token
}
func TestSaveToken(t *testing.T) {
f, err := ioutil.TempFile("", "testloadtoken")
if err != nil {
t.Fatalf("azure: unexpected error when creating temp file: %v", err)
}
defer os.Remove(f.Name())
f.Close()
mode := os.ModePerm & 0642
err = SaveToken(f.Name(), mode, *token())
if err != nil {
t.Fatalf("azure: unexpected error saving token to file: %v", err)
}
fi, err := os.Stat(f.Name()) // open a new stat as held ones are not fresh
if err != nil {
t.Fatalf("azure: stat failed: %v", err)
}
if runtime.GOOS != "windows" { // permissions don't work on Windows
if perm := fi.Mode().Perm(); perm != mode {
t.Fatalf("azure: wrong file perm. got:%s; expected:%s file :%s", perm, mode, f.Name())
}
}
var actualToken Token
var expectedToken Token
json.Unmarshal([]byte(MockTokenJSON), expectedToken)
contents, err := ioutil.ReadFile(f.Name())
if err != nil {
t.Fatal("!!")
}
json.Unmarshal(contents, actualToken)
if !reflect.DeepEqual(actualToken, expectedToken) {
t.Fatal("azure: token was not serialized correctly")
}
}
func TestSaveTokenFailsNoPermission(t *testing.T) {
pathWhereWeShouldntHavePermission := "/usr/thiswontwork/atall"
if runtime.GOOS == "windows" {
pathWhereWeShouldntHavePermission = path.Join(os.Getenv("windir"), "system32\\mytokendir\\mytoken")
}
err := SaveToken(pathWhereWeShouldntHavePermission, 0644, *token())
expectedSubstring := "failed to create directory"
if err == nil || !strings.Contains(err.Error(), expectedSubstring) {
t.Fatalf("azure: failed to get correct error expected(%s) actual(%v)", expectedSubstring, err)
}
}
func TestSaveTokenFailsCantCreate(t *testing.T) {
tokenPath := "/thiswontwork"
if runtime.GOOS == "windows" {
tokenPath = path.Join(os.Getenv("windir"), "system32")
}
err := SaveToken(tokenPath, 0644, *token())
expectedSubstring := "failed to create the temp file to write the token"
if err == nil || !strings.Contains(err.Error(), expectedSubstring) {
t.Fatalf("azure: failed to get correct error expected(%s) actual(%v)", expectedSubstring, err)
}
}

View file

@ -27,6 +27,7 @@ import (
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/Azure/go-autorest/autorest/date"
@ -42,9 +43,15 @@ const (
// OAuthGrantTypeClientCredentials is the "grant_type" identifier used in credential flows
OAuthGrantTypeClientCredentials = "client_credentials"
// OAuthGrantTypeUserPass is the "grant_type" identifier used in username and password auth flows
OAuthGrantTypeUserPass = "password"
// OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows
OAuthGrantTypeRefreshToken = "refresh_token"
// OAuthGrantTypeAuthorizationCode is the "grant_type" identifier used in authorization code flows
OAuthGrantTypeAuthorizationCode = "authorization_code"
// metadataHeader is the header required by MSI extension
metadataHeader = "Metadata"
)
@ -54,6 +61,12 @@ type OAuthTokenProvider interface {
OAuthToken() string
}
// TokenRefreshError is an interface used by errors returned during token refresh.
type TokenRefreshError interface {
error
Response() *http.Response
}
// Refresher is an interface for token refresh functionality
type Refresher interface {
Refresh() error
@ -78,6 +91,11 @@ type Token struct {
Type string `json:"token_type"`
}
// IsZero returns true if the token object is zero-initialized.
func (t Token) IsZero() bool {
return t == Token{}
}
// Expires returns the time.Time when the Token expires.
func (t Token) Expires() time.Time {
s, err := strconv.Atoi(t.ExpiresOn)
@ -145,6 +163,34 @@ type ServicePrincipalCertificateSecret struct {
type ServicePrincipalMSISecret struct {
}
// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth.
type ServicePrincipalUsernamePasswordSecret struct {
Username string
Password string
}
// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth.
type ServicePrincipalAuthorizationCodeSecret struct {
ClientSecret string
AuthorizationCode string
RedirectURI string
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
v.Set("code", secret.AuthorizationCode)
v.Set("client_secret", secret.ClientSecret)
v.Set("redirect_uri", secret.RedirectURI)
return nil
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
v.Set("username", secret.Username)
v.Set("password", secret.Password)
return nil
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
return nil
@ -199,25 +245,46 @@ func (secret *ServicePrincipalCertificateSecret) SetAuthenticationValues(spt *Se
type ServicePrincipalToken struct {
Token
secret ServicePrincipalSecret
oauthConfig OAuthConfig
clientID string
resource string
autoRefresh bool
refreshWithin time.Duration
sender Sender
secret ServicePrincipalSecret
oauthConfig OAuthConfig
clientID string
resource string
autoRefresh bool
autoRefreshLock *sync.Mutex
refreshWithin time.Duration
sender Sender
refreshCallbacks []TokenRefreshCallback
}
func validateOAuthConfig(oac OAuthConfig) error {
if oac.IsZero() {
return fmt.Errorf("parameter 'oauthConfig' cannot be zero-initialized")
}
return nil
}
// NewServicePrincipalTokenWithSecret create a ServicePrincipalToken using the supplied ServicePrincipalSecret implementation.
func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, resource string, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(id, "id"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if secret == nil {
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
}
spt := &ServicePrincipalToken{
oauthConfig: oauthConfig,
secret: secret,
clientID: id,
resource: resource,
autoRefresh: true,
autoRefreshLock: &sync.Mutex{},
refreshWithin: defaultRefresh,
sender: &http.Client{},
refreshCallbacks: callbacks,
@ -227,6 +294,18 @@ func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, reso
// NewServicePrincipalTokenFromManualToken creates a ServicePrincipalToken using the supplied token
func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID string, resource string, token Token, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if token.IsZero() {
return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized")
}
spt, err := NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
@ -245,6 +324,18 @@ func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID s
// NewServicePrincipalToken creates a ServicePrincipalToken from the supplied Service Principal
// credentials scoped to the named resource.
func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(secret, "secret"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
@ -256,8 +347,23 @@ func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret s
)
}
// NewServicePrincipalTokenFromCertificate create a ServicePrincipalToken from the supplied pkcs12 bytes.
// NewServicePrincipalTokenFromCertificate creates a ServicePrincipalToken from the supplied pkcs12 bytes.
func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID string, certificate *x509.Certificate, privateKey *rsa.PrivateKey, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if certificate == nil {
return nil, fmt.Errorf("parameter 'certificate' cannot be nil")
}
if privateKey == nil {
return nil, fmt.Errorf("parameter 'privateKey' cannot be nil")
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
@ -270,6 +376,70 @@ func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID s
)
}
// NewServicePrincipalTokenFromUsernamePassword creates a ServicePrincipalToken from the username and password.
func NewServicePrincipalTokenFromUsernamePassword(oauthConfig OAuthConfig, clientID string, username string, password string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(username, "username"); err != nil {
return nil, err
}
if err := validateStringParam(password, "password"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
resource,
&ServicePrincipalUsernamePasswordSecret{
Username: username,
Password: password,
},
callbacks...,
)
}
// NewServicePrincipalTokenFromAuthorizationCode creates a ServicePrincipalToken from the
func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clientID string, clientSecret string, authorizationCode string, redirectURI string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(clientSecret, "clientSecret"); err != nil {
return nil, err
}
if err := validateStringParam(authorizationCode, "authorizationCode"); err != nil {
return nil, err
}
if err := validateStringParam(redirectURI, "redirectURI"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
resource,
&ServicePrincipalAuthorizationCodeSecret{
ClientSecret: clientSecret,
AuthorizationCode: authorizationCode,
RedirectURI: redirectURI,
},
callbacks...,
)
}
// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines.
func GetMSIVMEndpoint() (string, error) {
return getMSIVMEndpoint(msiPath)
@ -293,7 +463,29 @@ func getMSIVMEndpoint(path string) (string, error) {
}
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
// It will use the system assigned identity when creating the token.
func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...)
}
// NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension.
// It will use the specified user assigned identity when creating the token.
func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...)
}
func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if userAssignedID != nil {
if err := validateStringParam(*userAssignedID, "userAssignedID"); err != nil {
return nil, err
}
}
// We set the oauth config token endpoint to be MSI's endpoint
msiEndpointURL, err := url.Parse(msiEndpoint)
if err != nil {
@ -310,19 +502,49 @@ func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...
secret: &ServicePrincipalMSISecret{},
resource: resource,
autoRefresh: true,
autoRefreshLock: &sync.Mutex{},
refreshWithin: defaultRefresh,
sender: &http.Client{},
refreshCallbacks: callbacks,
}
if userAssignedID != nil {
spt.clientID = *userAssignedID
}
return spt, nil
}
// internal type that implements TokenRefreshError
type tokenRefreshError struct {
message string
resp *http.Response
}
// Error implements the error interface which is part of the TokenRefreshError interface.
func (tre tokenRefreshError) Error() string {
return tre.message
}
// Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation.
func (tre tokenRefreshError) Response() *http.Response {
return tre.resp
}
func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError {
return tokenRefreshError{message: message, resp: resp}
}
// EnsureFresh will refresh the token if it will expire within the refresh window (as set by
// RefreshWithin) and autoRefresh flag is on.
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
func (spt *ServicePrincipalToken) EnsureFresh() error {
if spt.autoRefresh && spt.WillExpireIn(spt.refreshWithin) {
return spt.Refresh()
// take the lock then check to see if the token was already refreshed
spt.autoRefreshLock.Lock()
defer spt.autoRefreshLock.Unlock()
if spt.WillExpireIn(spt.refreshWithin) {
return spt.Refresh()
}
}
return nil
}
@ -341,15 +563,28 @@ func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error {
}
// Refresh obtains a fresh token for the Service Principal.
// This method is not safe for concurrent use and should be syncrhonized.
func (spt *ServicePrincipalToken) Refresh() error {
return spt.refreshInternal(spt.resource)
}
// RefreshExchange refreshes the token, but for a different resource.
// This method is not safe for concurrent use and should be syncrhonized.
func (spt *ServicePrincipalToken) RefreshExchange(resource string) error {
return spt.refreshInternal(resource)
}
func (spt *ServicePrincipalToken) getGrantType() string {
switch spt.secret.(type) {
case *ServicePrincipalUsernamePasswordSecret:
return OAuthGrantTypeUserPass
case *ServicePrincipalAuthorizationCodeSecret:
return OAuthGrantTypeAuthorizationCode
default:
return OAuthGrantTypeClientCredentials
}
}
func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
v := url.Values{}
v.Set("client_id", spt.clientID)
@ -359,7 +594,7 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
v.Set("grant_type", OAuthGrantTypeRefreshToken)
v.Set("refresh_token", spt.RefreshToken)
} else {
v.Set("grant_type", OAuthGrantTypeClientCredentials)
v.Set("grant_type", spt.getGrantType())
err := spt.secret.SetAuthenticationValues(spt, &v)
if err != nil {
return err
@ -388,9 +623,9 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
if resp.StatusCode != http.StatusOK {
if err != nil {
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode)
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode), resp)
}
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb))
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp)
}
if err != nil {

View file

@ -1,654 +0,0 @@
package adal
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/Azure/go-autorest/autorest/date"
"github.com/Azure/go-autorest/autorest/mocks"
)
const (
defaultFormData = "client_id=id&client_secret=secret&grant_type=client_credentials&resource=resource"
defaultManualFormData = "client_id=id&grant_type=refresh_token&refresh_token=refreshtoken&resource=resource"
)
func TestTokenExpires(t *testing.T) {
tt := time.Now().Add(5 * time.Second)
tk := newTokenExpiresAt(tt)
if tk.Expires().Equal(tt) {
t.Fatalf("adal: Token#Expires miscalculated expiration time -- received %v, expected %v", tk.Expires(), tt)
}
}
func TestTokenIsExpired(t *testing.T) {
tk := newTokenExpiresAt(time.Now().Add(-5 * time.Second))
if !tk.IsExpired() {
t.Fatalf("adal: Token#IsExpired failed to mark a stale token as expired -- now %v, token expires at %v",
time.Now().UTC(), tk.Expires())
}
}
func TestTokenIsExpiredUninitialized(t *testing.T) {
tk := &Token{}
if !tk.IsExpired() {
t.Fatalf("adal: An uninitialized Token failed to mark itself as expired (expiration time %v)", tk.Expires())
}
}
func TestTokenIsNoExpired(t *testing.T) {
tk := newTokenExpiresAt(time.Now().Add(1000 * time.Second))
if tk.IsExpired() {
t.Fatalf("adal: Token marked a fresh token as expired -- now %v, token expires at %v", time.Now().UTC(), tk.Expires())
}
}
func TestTokenWillExpireIn(t *testing.T) {
d := 5 * time.Second
tk := newTokenExpiresIn(d)
if !tk.WillExpireIn(d) {
t.Fatal("adal: Token#WillExpireIn mismeasured expiration time")
}
}
func TestServicePrincipalTokenSetAutoRefresh(t *testing.T) {
spt := newServicePrincipalToken()
if !spt.autoRefresh {
t.Fatal("adal: ServicePrincipalToken did not default to automatic token refreshing")
}
spt.SetAutoRefresh(false)
if spt.autoRefresh {
t.Fatal("adal: ServicePrincipalToken#SetAutoRefresh did not disable automatic token refreshing")
}
}
func TestServicePrincipalTokenSetRefreshWithin(t *testing.T) {
spt := newServicePrincipalToken()
if spt.refreshWithin != defaultRefresh {
t.Fatal("adal: ServicePrincipalToken did not correctly set the default refresh interval")
}
spt.SetRefreshWithin(2 * defaultRefresh)
if spt.refreshWithin != 2*defaultRefresh {
t.Fatal("adal: ServicePrincipalToken#SetRefreshWithin did not set the refresh interval")
}
}
func TestServicePrincipalTokenSetSender(t *testing.T) {
spt := newServicePrincipalToken()
c := &http.Client{}
spt.SetSender(c)
if !reflect.DeepEqual(c, spt.sender) {
t.Fatal("adal: ServicePrincipalToken#SetSender did not set the sender")
}
}
func TestServicePrincipalTokenRefreshUsesPOST(t *testing.T) {
spt := newServicePrincipalToken()
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "POST" {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set HTTP method -- expected %v, received %v", "POST", r.Method)
}
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
if body.IsOpen() {
t.Fatalf("the response was not closed!")
}
}
func TestServicePrincipalTokenFromMSIRefreshUsesPOST(t *testing.T) {
resource := "https://resource"
cb := func(token Token) error { return nil }
spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb)
if err != nil {
t.Fatalf("Failed to get MSI SPT: %v", err)
}
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "POST" {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set HTTP method -- expected %v, received %v", "POST", r.Method)
}
if h := r.Header.Get("Metadata"); h != "true" {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set Metadata header for MSI")
}
return resp, nil
})
}
})())
spt.SetSender(s)
err = spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
if body.IsOpen() {
t.Fatalf("the response was not closed!")
}
}
func TestServicePrincipalTokenRefreshSetsMimeType(t *testing.T) {
spt := newServicePrincipalToken()
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Header.Get(http.CanonicalHeaderKey("Content-Type")) != "application/x-www-form-urlencoded" {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set Content-Type -- expected %v, received %v",
"application/x-form-urlencoded",
r.Header.Get(http.CanonicalHeaderKey("Content-Type")))
}
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
}
func TestServicePrincipalTokenRefreshSetsURL(t *testing.T) {
spt := newServicePrincipalToken()
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.URL.String() != TestOAuthConfig.TokenEndpoint.String() {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set the URL -- expected %v, received %v",
TestOAuthConfig.TokenEndpoint, r.URL)
}
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
}
func testServicePrincipalTokenRefreshSetsBody(t *testing.T, spt *ServicePrincipalToken, f func(*testing.T, []byte)) {
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("adal: Failed to read body of Service Principal token request (%v)", err)
}
f(t, b)
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
}
func TestServicePrincipalTokenManualRefreshSetsBody(t *testing.T) {
sptManual := newServicePrincipalTokenManual()
testServicePrincipalTokenRefreshSetsBody(t, sptManual, func(t *testing.T, b []byte) {
if string(b) != defaultManualFormData {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set the HTTP Request Body -- expected %v, received %v",
defaultManualFormData, string(b))
}
})
}
func TestServicePrincipalTokenCertficateRefreshSetsBody(t *testing.T) {
sptCert := newServicePrincipalTokenCertificate(t)
testServicePrincipalTokenRefreshSetsBody(t, sptCert, func(t *testing.T, b []byte) {
body := string(b)
values, _ := url.ParseQuery(body)
if values["client_assertion_type"][0] != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" ||
values["client_id"][0] != "id" ||
values["grant_type"][0] != "client_credentials" ||
values["resource"][0] != "resource" {
t.Fatalf("adal: ServicePrincipalTokenCertificate#Refresh did not correctly set the HTTP Request Body.")
}
})
}
func TestServicePrincipalTokenSecretRefreshSetsBody(t *testing.T) {
spt := newServicePrincipalToken()
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
if string(b) != defaultFormData {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set the HTTP Request Body -- expected %v, received %v",
defaultFormData, string(b))
}
})
}
func TestServicePrincipalTokenRefreshClosesRequestBody(t *testing.T) {
spt := newServicePrincipalToken()
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
if resp.Body.(*mocks.Body).IsOpen() {
t.Fatal("adal: ServicePrincipalToken#Refresh failed to close the HTTP Response Body")
}
}
func TestServicePrincipalTokenRefreshRejectsResponsesWithStatusNotOK(t *testing.T) {
spt := newServicePrincipalToken()
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusUnauthorized, "Unauthorized")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err == nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh should reject a response with status != %d", http.StatusOK)
}
}
func TestServicePrincipalTokenRefreshRejectsEmptyBody(t *testing.T) {
spt := newServicePrincipalToken()
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
return mocks.NewResponse(), nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err == nil {
t.Fatal("adal: ServicePrincipalToken#Refresh should reject an empty token")
}
}
func TestServicePrincipalTokenRefreshPropagatesErrors(t *testing.T) {
spt := newServicePrincipalToken()
c := mocks.NewSender()
c.SetError(fmt.Errorf("Faux Error"))
spt.SetSender(c)
err := spt.Refresh()
if err == nil {
t.Fatal("adal: Failed to propagate the request error")
}
}
func TestServicePrincipalTokenRefreshReturnsErrorIfNotOk(t *testing.T) {
spt := newServicePrincipalToken()
c := mocks.NewSender()
c.AppendResponse(mocks.NewResponseWithStatus("401 NotAuthorized", http.StatusUnauthorized))
spt.SetSender(c)
err := spt.Refresh()
if err == nil {
t.Fatalf("adal: Failed to return an when receiving a status code other than HTTP %d", http.StatusOK)
}
}
func TestServicePrincipalTokenRefreshUnmarshals(t *testing.T) {
spt := newServicePrincipalToken()
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds()))
j := newTokenJSON(expiresOn, "resource")
resp := mocks.NewResponseWithContent(j)
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
} else if spt.AccessToken != "accessToken" ||
spt.ExpiresIn != "3600" ||
spt.ExpiresOn != expiresOn ||
spt.NotBefore != expiresOn ||
spt.Resource != "resource" ||
spt.Type != "Bearer" {
t.Fatalf("adal: ServicePrincipalToken#Refresh failed correctly unmarshal the JSON -- expected %v, received %v",
j, *spt)
}
}
func TestServicePrincipalTokenEnsureFreshRefreshes(t *testing.T) {
spt := newServicePrincipalToken()
expireToken(&spt.Token)
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
f := false
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
f = true
return resp, nil
})
}
})())
spt.SetSender(s)
err := spt.EnsureFresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#EnsureFresh returned an unexpected error (%v)", err)
}
if !f {
t.Fatal("adal: ServicePrincipalToken#EnsureFresh failed to call Refresh for stale token")
}
}
func TestServicePrincipalTokenEnsureFreshSkipsIfFresh(t *testing.T) {
spt := newServicePrincipalToken()
setTokenToExpireIn(&spt.Token, 1000*time.Second)
f := false
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
f = true
return mocks.NewResponse(), nil
})
}
})())
spt.SetSender(s)
err := spt.EnsureFresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#EnsureFresh returned an unexpected error (%v)", err)
}
if f {
t.Fatal("adal: ServicePrincipalToken#EnsureFresh invoked Refresh for fresh token")
}
}
func TestRefreshCallback(t *testing.T) {
callbackTriggered := false
spt := newServicePrincipalToken(func(Token) error {
callbackTriggered = true
return nil
})
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds()))
sender := mocks.NewSender()
j := newTokenJSON(expiresOn, "resource")
sender.AppendResponse(mocks.NewResponseWithContent(j))
spt.SetSender(sender)
err := spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
if !callbackTriggered {
t.Fatalf("adal: RefreshCallback failed to trigger call callback")
}
}
func TestRefreshCallbackErrorPropagates(t *testing.T) {
errorText := "this is an error text"
spt := newServicePrincipalToken(func(Token) error {
return fmt.Errorf(errorText)
})
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds()))
sender := mocks.NewSender()
j := newTokenJSON(expiresOn, "resource")
sender.AppendResponse(mocks.NewResponseWithContent(j))
spt.SetSender(sender)
err := spt.Refresh()
if err == nil || !strings.Contains(err.Error(), errorText) {
t.Fatalf("adal: RefreshCallback failed to propagate error")
}
}
// This demonstrates the danger of manual token without a refresh token
func TestServicePrincipalTokenManualRefreshFailsWithoutRefresh(t *testing.T) {
spt := newServicePrincipalTokenManual()
spt.RefreshToken = ""
err := spt.Refresh()
if err == nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh should have failed with a ManualTokenSecret without a refresh token")
}
}
func TestNewServicePrincipalTokenFromMSI(t *testing.T) {
resource := "https://resource"
cb := func(token Token) error { return nil }
spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb)
if err != nil {
t.Fatalf("Failed to get MSI SPT: %v", err)
}
// check some of the SPT fields
if _, ok := spt.secret.(*ServicePrincipalMSISecret); !ok {
t.Fatal("SPT secret was not of MSI type")
}
if spt.resource != resource {
t.Fatal("SPT came back with incorrect resource")
}
if len(spt.refreshCallbacks) != 1 {
t.Fatal("SPT had incorrect refresh callbacks.")
}
}
func TestGetVMEndpoint(t *testing.T) {
tempSettingsFile, err := ioutil.TempFile("", "ManagedIdentity-Settings")
if err != nil {
t.Fatal("Couldn't write temp settings file")
}
defer os.Remove(tempSettingsFile.Name())
settingsContents := []byte(`{
"url": "http://msiendpoint/"
}`)
if _, err := tempSettingsFile.Write(settingsContents); err != nil {
t.Fatal("Couldn't fill temp settings file")
}
endpoint, err := getMSIVMEndpoint(tempSettingsFile.Name())
if err != nil {
t.Fatal("Coudn't get VM endpoint")
}
if endpoint != "http://msiendpoint/" {
t.Fatal("Didn't get correct endpoint")
}
}
func newToken() *Token {
return &Token{
AccessToken: "ASECRETVALUE",
Resource: "https://azure.microsoft.com/",
Type: "Bearer",
}
}
func newTokenJSON(expiresOn string, resource string) string {
return fmt.Sprintf(`{
"access_token" : "accessToken",
"expires_in" : "3600",
"expires_on" : "%s",
"not_before" : "%s",
"resource" : "%s",
"token_type" : "Bearer"
}`,
expiresOn, expiresOn, resource)
}
func newTokenExpiresIn(expireIn time.Duration) *Token {
return setTokenToExpireIn(newToken(), expireIn)
}
func newTokenExpiresAt(expireAt time.Time) *Token {
return setTokenToExpireAt(newToken(), expireAt)
}
func expireToken(t *Token) *Token {
return setTokenToExpireIn(t, 0)
}
func setTokenToExpireAt(t *Token, expireAt time.Time) *Token {
t.ExpiresIn = "3600"
t.ExpiresOn = strconv.Itoa(int(expireAt.Sub(date.UnixEpoch()).Seconds()))
t.NotBefore = t.ExpiresOn
return t
}
func setTokenToExpireIn(t *Token, expireIn time.Duration) *Token {
return setTokenToExpireAt(t, time.Now().Add(expireIn))
}
func newServicePrincipalToken(callbacks ...TokenRefreshCallback) *ServicePrincipalToken {
spt, _ := NewServicePrincipalToken(TestOAuthConfig, "id", "secret", "resource", callbacks...)
return spt
}
func newServicePrincipalTokenManual() *ServicePrincipalToken {
token := newToken()
token.RefreshToken = "refreshtoken"
spt, _ := NewServicePrincipalTokenFromManualToken(TestOAuthConfig, "id", "resource", *token)
return spt
}
func newServicePrincipalTokenCertificate(t *testing.T) *ServicePrincipalToken {
template := x509.Certificate{
SerialNumber: big.NewInt(0),
Subject: pkix.Name{CommonName: "test"},
BasicConstraintsValid: true,
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
t.Fatal(err)
}
certificate, err := x509.ParseCertificate(certificateBytes)
if err != nil {
t.Fatal(err)
}
spt, _ := NewServicePrincipalTokenFromCertificate(TestOAuthConfig, "id", certificate, privateKey, "resource")
return spt
}

View file

@ -24,9 +24,12 @@ import (
)
const (
bearerChallengeHeader = "Www-Authenticate"
bearer = "Bearer"
tenantID = "tenantID"
bearerChallengeHeader = "Www-Authenticate"
bearer = "Bearer"
tenantID = "tenantID"
apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key"
bingAPISdkHeader = "X-BingApis-SDK-Client"
golangBingAPISdkHeaderValue = "Go-SDK"
)
// Authorizer is the interface that provides a PrepareDecorator used to supply request
@ -44,6 +47,53 @@ func (na NullAuthorizer) WithAuthorization() PrepareDecorator {
return WithNothing()
}
// APIKeyAuthorizer implements API Key authorization.
type APIKeyAuthorizer struct {
headers map[string]interface{}
queryParameters map[string]interface{}
}
// NewAPIKeyAuthorizerWithHeaders creates an ApiKeyAuthorizer with headers.
func NewAPIKeyAuthorizerWithHeaders(headers map[string]interface{}) *APIKeyAuthorizer {
return NewAPIKeyAuthorizer(headers, nil)
}
// NewAPIKeyAuthorizerWithQueryParameters creates an ApiKeyAuthorizer with query parameters.
func NewAPIKeyAuthorizerWithQueryParameters(queryParameters map[string]interface{}) *APIKeyAuthorizer {
return NewAPIKeyAuthorizer(nil, queryParameters)
}
// NewAPIKeyAuthorizer creates an ApiKeyAuthorizer with headers.
func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[string]interface{}) *APIKeyAuthorizer {
return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters}
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Paramaters
func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters))
}
}
// CognitiveServicesAuthorizer implements authorization for Cognitive Services.
type CognitiveServicesAuthorizer struct {
subscriptionKey string
}
// NewCognitiveServicesAuthorizer is
func NewCognitiveServicesAuthorizer(subscriptionKey string) *CognitiveServicesAuthorizer {
return &CognitiveServicesAuthorizer{subscriptionKey: subscriptionKey}
}
// WithAuthorization is
func (csa *CognitiveServicesAuthorizer) WithAuthorization() PrepareDecorator {
headers := make(map[string]interface{})
headers[apiKeyAuthorizerHeader] = csa.subscriptionKey
headers[bingAPISdkHeader] = golangBingAPISdkHeaderValue
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
}
// BearerAuthorizer implements the bearer authorization
type BearerAuthorizer struct {
tokenProvider adal.OAuthTokenProvider
@ -69,7 +119,11 @@ func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator {
if ok {
err := refresher.EnsureFresh()
if err != nil {
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", nil,
var resp *http.Response
if tokError, ok := err.(adal.TokenRefreshError); ok {
resp = tokError.Response()
}
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp,
"Failed to refresh the Token for request to %s", r.URL)
}
}
@ -179,3 +233,22 @@ func newBearerChallenge(resp *http.Response) (bc bearerChallenge, err error) {
return bc, err
}
// EventGridKeyAuthorizer implements authorization for event grid using key authentication.
type EventGridKeyAuthorizer struct {
topicKey string
}
// NewEventGridKeyAuthorizer creates a new EventGridKeyAuthorizer
// with the specified topic key.
func NewEventGridKeyAuthorizer(topicKey string) EventGridKeyAuthorizer {
return EventGridKeyAuthorizer{topicKey: topicKey}
}
// WithAuthorization returns a PrepareDecorator that adds the aeg-sas-key authentication header.
func (egta EventGridKeyAuthorizer) WithAuthorization() PrepareDecorator {
headers := map[string]interface{}{
"aeg-sas-key": egta.topicKey,
}
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
}

View file

@ -1,188 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"fmt"
"net/http"
"reflect"
"testing"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/mocks"
)
const (
TestTenantID = "TestTenantID"
TestActiveDirectoryEndpoint = "https://login/test.com/"
)
func TestWithAuthorizer(t *testing.T) {
r1 := mocks.NewRequest()
na := &NullAuthorizer{}
r2, err := Prepare(r1,
na.WithAuthorization())
if err != nil {
t.Fatalf("autorest: NullAuthorizer#WithAuthorization returned an unexpected error (%v)", err)
} else if !reflect.DeepEqual(r1, r2) {
t.Fatalf("autorest: NullAuthorizer#WithAuthorization modified the request -- received %v, expected %v", r2, r1)
}
}
func TestTokenWithAuthorization(t *testing.T) {
token := &adal.Token{
AccessToken: "TestToken",
Resource: "https://azure.microsoft.com/",
Type: "Bearer",
}
ba := NewBearerAuthorizer(token)
req, err := Prepare(&http.Request{}, ba.WithAuthorization())
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
} else if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != fmt.Sprintf("Bearer %s", token.AccessToken) {
t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to set Authorization header")
}
}
func TestServicePrincipalTokenWithAuthorizationNoRefresh(t *testing.T) {
oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID)
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
}
spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", "resource", nil)
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
}
spt.SetAutoRefresh(false)
s := mocks.NewSender()
spt.SetSender(s)
ba := NewBearerAuthorizer(spt)
req, err := Prepare(mocks.NewRequest(), ba.WithAuthorization())
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
} else if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != fmt.Sprintf("Bearer %s", spt.AccessToken) {
t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to set Authorization header")
}
}
func TestServicePrincipalTokenWithAuthorizationRefresh(t *testing.T) {
oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID)
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
}
refreshed := false
spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", "resource", func(t adal.Token) error {
refreshed = true
return nil
})
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
}
jwt := `{
"access_token" : "accessToken",
"expires_in" : "3600",
"expires_on" : "test",
"not_before" : "test",
"resource" : "test",
"token_type" : "Bearer"
}`
body := mocks.NewBody(jwt)
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
return resp, nil
})
}
})())
spt.SetSender(s)
ba := NewBearerAuthorizer(spt)
req, err := Prepare(mocks.NewRequest(), ba.WithAuthorization())
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
} else if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != fmt.Sprintf("Bearer %s", spt.AccessToken) {
t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to set Authorization header")
}
if !refreshed {
t.Fatal("azure: BearerAuthorizer#WithAuthorization must refresh the token")
}
}
func TestServicePrincipalTokenWithAuthorizationReturnsErrorIfConnotRefresh(t *testing.T) {
oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID)
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
}
spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", "resource", nil)
if err != nil {
t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err)
}
s := mocks.NewSender()
s.AppendResponse(mocks.NewResponseWithStatus("400 Bad Request", http.StatusBadRequest))
spt.SetSender(s)
ba := NewBearerAuthorizer(spt)
_, err = Prepare(mocks.NewRequest(), ba.WithAuthorization())
if err == nil {
t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to return an error when refresh fails")
}
}
func TestBearerAuthorizerCallback(t *testing.T) {
tenantString := "123-tenantID-456"
resourceString := "https://fake.resource.net"
s := mocks.NewSender()
resp := mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized)
mocks.SetResponseHeader(resp, bearerChallengeHeader, bearer+" \"authorization\"=\"https://fake.net/"+tenantString+"\",\"resource\"=\""+resourceString+"\"")
s.AppendResponse(resp)
auth := NewBearerAuthorizerCallback(s, func(tenantID, resource string) (*BearerAuthorizer, error) {
if tenantID != tenantString {
t.Fatal("BearerAuthorizerCallback: bad tenant ID")
}
if resource != resourceString {
t.Fatal("BearerAuthorizerCallback: bad resource")
}
oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, tenantID)
if err != nil {
t.Fatalf("azure: NewOAuthConfig returned an error (%v)", err)
}
spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", resource)
if err != nil {
t.Fatalf("azure: NewServicePrincipalToken returned an error (%v)", err)
}
spt.SetSender(s)
return NewBearerAuthorizer(spt), nil
})
_, err := Prepare(mocks.NewRequest(), auth.WithAuthorization())
if err == nil {
t.Fatal("azure: BearerAuthorizerCallback#WithAuthorization failed to return an error when refresh fails")
}
}

View file

@ -1,140 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"net/http"
"testing"
"github.com/Azure/go-autorest/autorest/mocks"
)
func TestResponseHasStatusCode(t *testing.T) {
codes := []int{http.StatusOK, http.StatusAccepted}
resp := &http.Response{StatusCode: http.StatusAccepted}
if !ResponseHasStatusCode(resp, codes...) {
t.Fatalf("autorest: ResponseHasStatusCode failed to find %v in %v", resp.StatusCode, codes)
}
}
func TestResponseHasStatusCodeNotPresent(t *testing.T) {
codes := []int{http.StatusOK, http.StatusAccepted}
resp := &http.Response{StatusCode: http.StatusInternalServerError}
if ResponseHasStatusCode(resp, codes...) {
t.Fatalf("autorest: ResponseHasStatusCode unexpectedly found %v in %v", resp.StatusCode, codes)
}
}
func TestNewPollingRequestDoesNotReturnARequestWhenLocationHeaderIsMissing(t *testing.T) {
resp := mocks.NewResponseWithStatus("500 InternalServerError", http.StatusInternalServerError)
req, _ := NewPollingRequest(resp, nil)
if req != nil {
t.Fatal("autorest: NewPollingRequest returned an http.Request when the Location header was missing")
}
}
func TestNewPollingRequestReturnsAnErrorWhenPrepareFails(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
resp.Header.Set(http.CanonicalHeaderKey(HeaderLocation), mocks.TestBadURL)
_, err := NewPollingRequest(resp, nil)
if err == nil {
t.Fatal("autorest: NewPollingRequest failed to return an error when Prepare fails")
}
}
func TestNewPollingRequestDoesNotReturnARequestWhenPrepareFails(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
resp.Header.Set(http.CanonicalHeaderKey(HeaderLocation), mocks.TestBadURL)
req, _ := NewPollingRequest(resp, nil)
if req != nil {
t.Fatal("autorest: NewPollingRequest returned an http.Request when Prepare failed")
}
}
func TestNewPollingRequestReturnsAGetRequest(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
req, _ := NewPollingRequest(resp, nil)
if req.Method != "GET" {
t.Fatalf("autorest: NewPollingRequest did not create an HTTP GET request -- actual method %v", req.Method)
}
}
func TestNewPollingRequestProvidesTheURL(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
req, _ := NewPollingRequest(resp, nil)
if req.URL.String() != mocks.TestURL {
t.Fatalf("autorest: NewPollingRequest did not create an HTTP with the expected URL -- received %v, expected %v", req.URL, mocks.TestURL)
}
}
func TestGetLocation(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
l := GetLocation(resp)
if len(l) == 0 {
t.Fatalf("autorest: GetLocation failed to return Location header -- expected %v, received %v", mocks.TestURL, l)
}
}
func TestGetLocationReturnsEmptyStringForMissingLocation(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
l := GetLocation(resp)
if len(l) != 0 {
t.Fatalf("autorest: GetLocation return a value without a Location header -- received %v", l)
}
}
func TestGetRetryAfter(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
d := GetRetryAfter(resp, DefaultPollingDelay)
if d != mocks.TestDelay {
t.Fatalf("autorest: GetRetryAfter failed to returned the expected delay -- expected %v, received %v", mocks.TestDelay, d)
}
}
func TestGetRetryAfterReturnsDefaultDelayIfRetryHeaderIsMissing(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
d := GetRetryAfter(resp, DefaultPollingDelay)
if d != DefaultPollingDelay {
t.Fatalf("autorest: GetRetryAfter failed to returned the default delay for a missing Retry-After header -- expected %v, received %v",
DefaultPollingDelay, d)
}
}
func TestGetRetryAfterReturnsDefaultDelayIfRetryHeaderIsMalformed(t *testing.T) {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
resp.Header.Set(http.CanonicalHeaderKey(HeaderRetryAfter), "a very bad non-integer value")
d := GetRetryAfter(resp, DefaultPollingDelay)
if d != DefaultPollingDelay {
t.Fatalf("autorest: GetRetryAfter failed to returned the default delay for a malformed Retry-After header -- expected %v, received %v",
DefaultPollingDelay, d)
}
}

View file

@ -39,7 +39,7 @@ const (
operationSucceeded string = "Succeeded"
)
var pollingCodes = [...]int{http.StatusAccepted, http.StatusCreated, http.StatusOK}
var pollingCodes = [...]int{http.StatusNoContent, http.StatusAccepted, http.StatusCreated, http.StatusOK}
// Future provides a mechanism to access the status and results of an asynchronous request.
// Since futures are stateful they should be passed by value to avoid race conditions.
@ -234,20 +234,15 @@ func getAsyncOperation(resp *http.Response) string {
}
func hasSucceeded(state string) bool {
return state == operationSucceeded
return strings.EqualFold(state, operationSucceeded)
}
func hasTerminated(state string) bool {
switch state {
case operationCanceled, operationFailed, operationSucceeded:
return true
default:
return false
}
return strings.EqualFold(state, operationCanceled) || strings.EqualFold(state, operationFailed) || strings.EqualFold(state, operationSucceeded)
}
func hasFailed(state string) bool {
return state == operationFailed
return strings.EqualFold(state, operationFailed)
}
type provisioningTracker interface {
@ -426,7 +421,7 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
}
}
if ps.State == operationInProgress && ps.URI == "" {
if strings.EqualFold(ps.State, operationInProgress) && ps.URI == "" {
return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Unable to obtain polling URI for %s %s", resp.Request.Method, resp.Request.URL)
}
@ -463,3 +458,21 @@ func newPollingRequest(ps pollingState) (*http.Request, error) {
return reqPoll, nil
}
// AsyncOpIncompleteError is the type that's returned from a future that has not completed.
type AsyncOpIncompleteError struct {
// FutureType is the name of the type composed of a azure.Future.
FutureType string
}
// Error returns an error message including the originating type name of the error.
func (e AsyncOpIncompleteError) Error() string {
return fmt.Sprintf("%s: asynchronous operation has not completed", e.FutureType)
}
// NewAsyncOpIncompleteError creates a new AsyncOpIncompleteError with the specified parameters.
func NewAsyncOpIncompleteError(futureType string) AsyncOpIncompleteError {
return AsyncOpIncompleteError{
FutureType: futureType,
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,513 +0,0 @@
package azure
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"testing"
"time"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/mocks"
)
const (
headerAuthorization = "Authorization"
longDelay = 5 * time.Second
retryDelay = 10 * time.Millisecond
testLogPrefix = "azure:"
)
// Use a Client Inspector to set the request identifier.
func ExampleWithClientID() {
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
req, _ := autorest.Prepare(&http.Request{},
autorest.AsGet(),
autorest.WithBaseURL("https://microsoft.com/a/b/c/"))
c := autorest.Client{Sender: mocks.NewSender()}
c.RequestInspector = WithReturningClientID(uuid)
autorest.SendWithSender(c, req)
fmt.Printf("Inspector added the %s header with the value %s\n",
HeaderClientID, req.Header.Get(HeaderClientID))
fmt.Printf("Inspector added the %s header with the value %s\n",
HeaderReturnClientID, req.Header.Get(HeaderReturnClientID))
// Output:
// Inspector added the x-ms-client-request-id header with the value 71FDB9F4-5E49-4C12-B266-DE7B4FD999A6
// Inspector added the x-ms-return-client-request-id header with the value true
}
func TestWithReturningClientIDReturnsError(t *testing.T) {
var errIn error
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
_, errOut := autorest.Prepare(&http.Request{},
withErrorPrepareDecorator(&errIn),
WithReturningClientID(uuid))
if errOut == nil || errIn != errOut {
t.Fatalf("azure: WithReturningClientID failed to exit early when receiving an error -- expected (%v), received (%v)",
errIn, errOut)
}
}
func TestWithClientID(t *testing.T) {
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
req, _ := autorest.Prepare(&http.Request{},
WithClientID(uuid))
if req.Header.Get(HeaderClientID) != uuid {
t.Fatalf("azure: WithClientID failed to set %s -- expected %s, received %s",
HeaderClientID, uuid, req.Header.Get(HeaderClientID))
}
}
func TestWithReturnClientID(t *testing.T) {
b := false
req, _ := autorest.Prepare(&http.Request{},
WithReturnClientID(b))
if req.Header.Get(HeaderReturnClientID) != strconv.FormatBool(b) {
t.Fatalf("azure: WithReturnClientID failed to set %s -- expected %s, received %s",
HeaderClientID, strconv.FormatBool(b), req.Header.Get(HeaderClientID))
}
}
func TestExtractClientID(t *testing.T) {
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
resp := mocks.NewResponse()
mocks.SetResponseHeader(resp, HeaderClientID, uuid)
if ExtractClientID(resp) != uuid {
t.Fatalf("azure: ExtractClientID failed to extract the %s -- expected %s, received %s",
HeaderClientID, uuid, ExtractClientID(resp))
}
}
func TestExtractRequestID(t *testing.T) {
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
resp := mocks.NewResponse()
mocks.SetResponseHeader(resp, HeaderRequestID, uuid)
if ExtractRequestID(resp) != uuid {
t.Fatalf("azure: ExtractRequestID failed to extract the %s -- expected %s, received %s",
HeaderRequestID, uuid, ExtractRequestID(resp))
}
}
func TestIsAzureError_ReturnsTrueForAzureError(t *testing.T) {
if !IsAzureError(&RequestError{}) {
t.Fatalf("azure: IsAzureError failed to return true for an Azure Service error")
}
}
func TestIsAzureError_ReturnsFalseForNonAzureError(t *testing.T) {
if IsAzureError(fmt.Errorf("An Error")) {
t.Fatalf("azure: IsAzureError return true for an non-Azure Service error")
}
}
func TestNewErrorWithError_UsesReponseStatusCode(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("Error"), "packageType", "method", mocks.NewResponseWithStatus("Forbidden", http.StatusForbidden), "message")
if e.StatusCode != http.StatusForbidden {
t.Fatalf("azure: NewErrorWithError failed to use the Status Code of the passed Response -- expected %v, received %v", http.StatusForbidden, e.StatusCode)
}
}
func TestNewErrorWithError_ReturnsUnwrappedError(t *testing.T) {
e1 := RequestError{}
e1.ServiceError = &ServiceError{Code: "42", Message: "A Message"}
e1.StatusCode = 200
e1.RequestID = "A RequestID"
e2 := NewErrorWithError(&e1, "packageType", "method", nil, "message")
if !reflect.DeepEqual(e1, e2) {
t.Fatalf("azure: NewErrorWithError wrapped an RequestError -- expected %T, received %T", e1, e2)
}
}
func TestNewErrorWithError_WrapsAnError(t *testing.T) {
e1 := fmt.Errorf("Inner Error")
var e2 interface{} = NewErrorWithError(e1, "packageType", "method", nil, "message")
if _, ok := e2.(RequestError); !ok {
t.Fatalf("azure: NewErrorWithError failed to wrap a standard error -- received %T", e2)
}
}
func TestWithErrorUnlessStatusCode_NotAnAzureError(t *testing.T) {
body := `<html>
<head>
<title>IIS Error page</title>
</head>
<body>Some non-JSON error page</body>
</html>`
r := mocks.NewResponseWithContent(body)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusBadRequest
r.Status = http.StatusText(r.StatusCode)
err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
ok, _ := err.(*RequestError)
if ok != nil {
t.Fatalf("azure: azure.RequestError returned from malformed response: %v", err)
}
// the error body should still be there
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
if string(b) != body {
t.Fatalf("response body is wrong. got=%q exptected=%q", string(b), body)
}
}
func TestWithErrorUnlessStatusCode_FoundAzureErrorWithoutDetails(t *testing.T) {
j := `{
"error": {
"code": "InternalError",
"message": "Azure is having trouble right now."
}
}`
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
r := mocks.NewResponseWithContent(j)
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(r.StatusCode)
err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
if err == nil {
t.Fatalf("azure: returned nil error for proper error response")
}
azErr, ok := err.(*RequestError)
if !ok {
t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
}
expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Azure is having trouble right now.\""
if !reflect.DeepEqual(expected, azErr.Error()) {
t.Fatalf("azure: service error is not unmarshaled properly.\nexpected=%v\ngot=%v", expected, azErr.Error())
}
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
t.Fatalf("azure: got wrong StatusCode=%d Expected=%d", azErr.StatusCode, expected)
}
if expected := uuid; azErr.RequestID != expected {
t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
}
_ = azErr.Error()
// the error body should still be there
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
if string(b) != j {
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
}
}
func TestWithErrorUnlessStatusCode_FoundAzureErrorWithDetails(t *testing.T) {
j := `{
"error": {
"code": "InternalError",
"message": "Azure is having trouble right now.",
"details": [{"code": "conflict1", "message":"error message1"},
{"code": "conflict2", "message":"error message2"}]
}
}`
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
r := mocks.NewResponseWithContent(j)
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(r.StatusCode)
err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
if err == nil {
t.Fatalf("azure: returned nil error for proper error response")
}
azErr, ok := err.(*RequestError)
if !ok {
t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
}
if expected := "InternalError"; azErr.ServiceError.Code != expected {
t.Fatalf("azure: wrong error code. expected=%q; got=%q", expected, azErr.ServiceError.Code)
}
if azErr.ServiceError.Message == "" {
t.Fatalf("azure: error message is not unmarshaled properly")
}
b, _ := json.Marshal(*azErr.ServiceError.Details)
if string(b) != `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]` {
t.Fatalf("azure: error details is not unmarshaled properly")
}
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected)
}
if expected := uuid; azErr.RequestID != expected {
t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
}
_ = azErr.Error()
// the error body should still be there
defer r.Body.Close()
b, err = ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
if string(b) != j {
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
}
}
func TestWithErrorUnlessStatusCode_NoAzureError(t *testing.T) {
j := `{
"Status":"NotFound"
}`
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
r := mocks.NewResponseWithContent(j)
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(r.StatusCode)
err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
if err == nil {
t.Fatalf("azure: returned nil error for proper error response")
}
azErr, ok := err.(*RequestError)
if !ok {
t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
}
expected := &ServiceError{
Code: "Unknown",
Message: "Unknown service error",
}
if !reflect.DeepEqual(expected, azErr.ServiceError) {
t.Fatalf("azure: service error is not unmarshaled properly. expected=%q\ngot=%q", expected, azErr.ServiceError)
}
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected)
}
if expected := uuid; azErr.RequestID != expected {
t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
}
_ = azErr.Error()
// the error body should still be there
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
if string(b) != j {
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
}
}
func TestWithErrorUnlessStatusCode_UnwrappedError(t *testing.T) {
j := `{
"target": null,
"code": "InternalError",
"message": "Azure is having trouble right now.",
"details": [{"code": "conflict1", "message":"error message1"},
{"code": "conflict2", "message":"error message2"}],
"innererror": []
}`
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
r := mocks.NewResponseWithContent(j)
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(r.StatusCode)
err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
if err == nil {
t.Fatal("azure: returned nil error for proper error response")
}
azErr, ok := err.(*RequestError)
if !ok {
t.Fatalf("returned error is not azure.RequestError: %T", err)
}
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
t.Logf("Incorrect StatusCode got: %v want: %d", azErr.StatusCode, expected)
t.Fail()
}
if expected := "Azure is having trouble right now."; azErr.ServiceError.Message != expected {
t.Logf("Incorrect Message\n\tgot: %q\n\twant: %q", azErr.Message, expected)
t.Fail()
}
if expected := uuid; azErr.RequestID != expected {
t.Logf("Incorrect request ID\n\tgot: %q\n\twant: %q", azErr.RequestID, expected)
t.Fail()
}
expectedServiceErrorDetails := `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]`
if azErr.ServiceError == nil {
t.Logf("`ServiceError` was nil when it shouldn't have been.")
t.Fail()
} else if azErr.ServiceError.Details == nil {
t.Logf("`ServiceError.Details` was nil when it should have been %q", expectedServiceErrorDetails)
t.Fail()
} else if details, _ := json.Marshal(*azErr.ServiceError.Details); expectedServiceErrorDetails != string(details) {
t.Logf("Error detaisl was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(details), expectedServiceErrorDetails)
t.Fail()
}
// the error body should still be there
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Error(err)
}
if string(b) != j {
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
}
}
func TestRequestErrorString_WithError(t *testing.T) {
j := `{
"error": {
"code": "InternalError",
"message": "Conflict",
"details": [{"code": "conflict1", "message":"error message1"}]
}
}`
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
r := mocks.NewResponseWithContent(j)
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(r.StatusCode)
err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
if err == nil {
t.Fatalf("azure: returned nil error for proper error response")
}
azErr, _ := err.(*RequestError)
expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Conflict\" Details=[{\"code\":\"conflict1\",\"message\":\"error message1\"}]"
if expected != azErr.Error() {
t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, azErr.Error())
}
}
func withErrorPrepareDecorator(e *error) autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
*e = fmt.Errorf("azure: Faux Prepare Error")
return r, *e
})
}
}
func withAsyncResponseDecorator(n int) autorest.SendDecorator {
i := 0
return func(s autorest.Sender) autorest.Sender {
return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
resp, err := s.Do(r)
if err == nil {
if i < n {
resp.StatusCode = http.StatusCreated
resp.Header = http.Header{}
resp.Header.Add(http.CanonicalHeaderKey(headerAsyncOperation), mocks.TestURL)
i++
} else {
resp.StatusCode = http.StatusOK
resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
}
}
return resp, err
})
}
}
type mockAuthorizer struct{}
func (ma mockAuthorizer) WithAuthorization() autorest.PrepareDecorator {
return autorest.WithHeader(headerAuthorization, mocks.TestAuthorizationHeader)
}
type mockFailingAuthorizer struct{}
func (mfa mockFailingAuthorizer) WithAuthorization() autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
return r, fmt.Errorf("ERROR: mockFailingAuthorizer returned expected error")
})
}
}
type mockInspector struct {
wasInvoked bool
}
func (mi *mockInspector) WithInspection() autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
mi.wasInvoked = true
return p.Prepare(r)
})
}
}
func (mi *mockInspector) ByInspecting() autorest.RespondDecorator {
return func(r autorest.Responder) autorest.Responder {
return autorest.ResponderFunc(func(resp *http.Response) error {
mi.wasInvoked = true
return r.Respond(resp)
})
}
}

View file

@ -83,10 +83,10 @@ var (
PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index",
ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/",
ResourceManagerEndpoint: "https://management.usgovcloudapi.net/",
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
ActiveDirectoryEndpoint: "https://login.microsoftonline.us/",
GalleryEndpoint: "https://gallery.usgovcloudapi.net/",
KeyVaultEndpoint: "https://vault.usgovcloudapi.net/",
GraphEndpoint: "https://graph.usgovcloudapi.net/",
GraphEndpoint: "https://graph.windows.net/",
StorageEndpointSuffix: "core.usgovcloudapi.net",
SQLDatabaseDNSSuffix: "database.usgovcloudapi.net",
TrafficManagerDNSSuffix: "usgovtrafficmanager.net",

View file

@ -1,284 +0,0 @@
// test
package azure
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"os"
"path"
"path/filepath"
"runtime"
"testing"
)
// This correlates to the expected contents of ./testdata/test_environment_1.json
var testEnvironment1 = Environment{
Name: "--unit-test--",
ManagementPortalURL: "--management-portal-url",
PublishSettingsURL: "--publish-settings-url--",
ServiceManagementEndpoint: "--service-management-endpoint--",
ResourceManagerEndpoint: "--resource-management-endpoint--",
ActiveDirectoryEndpoint: "--active-directory-endpoint--",
GalleryEndpoint: "--gallery-endpoint--",
KeyVaultEndpoint: "--key-vault--endpoint--",
GraphEndpoint: "--graph-endpoint--",
StorageEndpointSuffix: "--storage-endpoint-suffix--",
SQLDatabaseDNSSuffix: "--sql-database-dns-suffix--",
TrafficManagerDNSSuffix: "--traffic-manager-dns-suffix--",
KeyVaultDNSSuffix: "--key-vault-dns-suffix--",
ServiceBusEndpointSuffix: "--service-bus-endpoint-suffix--",
ServiceManagementVMDNSSuffix: "--asm-vm-dns-suffix--",
ResourceManagerVMDNSSuffix: "--arm-vm-dns-suffix--",
ContainerRegistryDNSSuffix: "--container-registry-dns-suffix--",
}
func TestEnvironment_EnvironmentFromFile(t *testing.T) {
got, err := EnvironmentFromFile(filepath.Join("testdata", "test_environment_1.json"))
if err != nil {
t.Error(err)
}
if got != testEnvironment1 {
t.Logf("got: %v want: %v", got, testEnvironment1)
t.Fail()
}
}
func TestEnvironment_EnvironmentFromName_Stack(t *testing.T) {
_, currentFile, _, _ := runtime.Caller(0)
prevEnvFilepathValue := os.Getenv(EnvironmentFilepathName)
os.Setenv(EnvironmentFilepathName, filepath.Join(path.Dir(currentFile), "testdata", "test_environment_1.json"))
defer os.Setenv(EnvironmentFilepathName, prevEnvFilepathValue)
got, err := EnvironmentFromName("AZURESTACKCLOUD")
if err != nil {
t.Error(err)
}
if got != testEnvironment1 {
t.Logf("got: %v want: %v", got, testEnvironment1)
t.Fail()
}
}
func TestEnvironmentFromName(t *testing.T) {
name := "azurechinacloud"
if env, _ := EnvironmentFromName(name); env != ChinaCloud {
t.Errorf("Expected to get ChinaCloud for %q", name)
}
name = "AzureChinaCloud"
if env, _ := EnvironmentFromName(name); env != ChinaCloud {
t.Errorf("Expected to get ChinaCloud for %q", name)
}
name = "azuregermancloud"
if env, _ := EnvironmentFromName(name); env != GermanCloud {
t.Errorf("Expected to get GermanCloud for %q", name)
}
name = "AzureGermanCloud"
if env, _ := EnvironmentFromName(name); env != GermanCloud {
t.Errorf("Expected to get GermanCloud for %q", name)
}
name = "azurepubliccloud"
if env, _ := EnvironmentFromName(name); env != PublicCloud {
t.Errorf("Expected to get PublicCloud for %q", name)
}
name = "AzurePublicCloud"
if env, _ := EnvironmentFromName(name); env != PublicCloud {
t.Errorf("Expected to get PublicCloud for %q", name)
}
name = "azureusgovernmentcloud"
if env, _ := EnvironmentFromName(name); env != USGovernmentCloud {
t.Errorf("Expected to get USGovernmentCloud for %q", name)
}
name = "AzureUSGovernmentCloud"
if env, _ := EnvironmentFromName(name); env != USGovernmentCloud {
t.Errorf("Expected to get USGovernmentCloud for %q", name)
}
name = "thisisnotarealcloudenv"
if _, err := EnvironmentFromName(name); err == nil {
t.Errorf("Expected to get an error for %q", name)
}
}
func TestDeserializeEnvironment(t *testing.T) {
env := `{
"name": "--name--",
"ActiveDirectoryEndpoint": "--active-directory-endpoint--",
"galleryEndpoint": "--gallery-endpoint--",
"graphEndpoint": "--graph-endpoint--",
"keyVaultDNSSuffix": "--key-vault-dns-suffix--",
"keyVaultEndpoint": "--key-vault-endpoint--",
"managementPortalURL": "--management-portal-url--",
"publishSettingsURL": "--publish-settings-url--",
"resourceManagerEndpoint": "--resource-manager-endpoint--",
"serviceBusEndpointSuffix": "--service-bus-endpoint-suffix--",
"serviceManagementEndpoint": "--service-management-endpoint--",
"sqlDatabaseDNSSuffix": "--sql-database-dns-suffix--",
"storageEndpointSuffix": "--storage-endpoint-suffix--",
"trafficManagerDNSSuffix": "--traffic-manager-dns-suffix--",
"serviceManagementVMDNSSuffix": "--asm-vm-dns-suffix--",
"resourceManagerVMDNSSuffix": "--arm-vm-dns-suffix--",
"containerRegistryDNSSuffix": "--container-registry-dns-suffix--"
}`
testSubject := Environment{}
err := json.Unmarshal([]byte(env), &testSubject)
if err != nil {
t.Fatalf("failed to unmarshal: %s", err)
}
if "--name--" != testSubject.Name {
t.Errorf("Expected Name to be \"--name--\", but got %q", testSubject.Name)
}
if "--management-portal-url--" != testSubject.ManagementPortalURL {
t.Errorf("Expected ManagementPortalURL to be \"--management-portal-url--\", but got %q", testSubject.ManagementPortalURL)
}
if "--publish-settings-url--" != testSubject.PublishSettingsURL {
t.Errorf("Expected PublishSettingsURL to be \"--publish-settings-url--\", but got %q", testSubject.PublishSettingsURL)
}
if "--service-management-endpoint--" != testSubject.ServiceManagementEndpoint {
t.Errorf("Expected ServiceManagementEndpoint to be \"--service-management-endpoint--\", but got %q", testSubject.ServiceManagementEndpoint)
}
if "--resource-manager-endpoint--" != testSubject.ResourceManagerEndpoint {
t.Errorf("Expected ResourceManagerEndpoint to be \"--resource-manager-endpoint--\", but got %q", testSubject.ResourceManagerEndpoint)
}
if "--active-directory-endpoint--" != testSubject.ActiveDirectoryEndpoint {
t.Errorf("Expected ActiveDirectoryEndpoint to be \"--active-directory-endpoint--\", but got %q", testSubject.ActiveDirectoryEndpoint)
}
if "--gallery-endpoint--" != testSubject.GalleryEndpoint {
t.Errorf("Expected GalleryEndpoint to be \"--gallery-endpoint--\", but got %q", testSubject.GalleryEndpoint)
}
if "--key-vault-endpoint--" != testSubject.KeyVaultEndpoint {
t.Errorf("Expected KeyVaultEndpoint to be \"--key-vault-endpoint--\", but got %q", testSubject.KeyVaultEndpoint)
}
if "--graph-endpoint--" != testSubject.GraphEndpoint {
t.Errorf("Expected GraphEndpoint to be \"--graph-endpoint--\", but got %q", testSubject.GraphEndpoint)
}
if "--storage-endpoint-suffix--" != testSubject.StorageEndpointSuffix {
t.Errorf("Expected StorageEndpointSuffix to be \"--storage-endpoint-suffix--\", but got %q", testSubject.StorageEndpointSuffix)
}
if "--sql-database-dns-suffix--" != testSubject.SQLDatabaseDNSSuffix {
t.Errorf("Expected sql-database-dns-suffix to be \"--sql-database-dns-suffix--\", but got %q", testSubject.SQLDatabaseDNSSuffix)
}
if "--key-vault-dns-suffix--" != testSubject.KeyVaultDNSSuffix {
t.Errorf("Expected StorageEndpointSuffix to be \"--key-vault-dns-suffix--\", but got %q", testSubject.KeyVaultDNSSuffix)
}
if "--service-bus-endpoint-suffix--" != testSubject.ServiceBusEndpointSuffix {
t.Errorf("Expected StorageEndpointSuffix to be \"--service-bus-endpoint-suffix--\", but got %q", testSubject.ServiceBusEndpointSuffix)
}
if "--asm-vm-dns-suffix--" != testSubject.ServiceManagementVMDNSSuffix {
t.Errorf("Expected ServiceManagementVMDNSSuffix to be \"--asm-vm-dns-suffix--\", but got %q", testSubject.ServiceManagementVMDNSSuffix)
}
if "--arm-vm-dns-suffix--" != testSubject.ResourceManagerVMDNSSuffix {
t.Errorf("Expected ResourceManagerVMDNSSuffix to be \"--arm-vm-dns-suffix--\", but got %q", testSubject.ResourceManagerVMDNSSuffix)
}
if "--container-registry-dns-suffix--" != testSubject.ContainerRegistryDNSSuffix {
t.Errorf("Expected ContainerRegistryDNSSuffix to be \"--container-registry-dns-suffix--\", but got %q", testSubject.ContainerRegistryDNSSuffix)
}
}
func TestRoundTripSerialization(t *testing.T) {
env := Environment{
Name: "--unit-test--",
ManagementPortalURL: "--management-portal-url",
PublishSettingsURL: "--publish-settings-url--",
ServiceManagementEndpoint: "--service-management-endpoint--",
ResourceManagerEndpoint: "--resource-management-endpoint--",
ActiveDirectoryEndpoint: "--active-directory-endpoint--",
GalleryEndpoint: "--gallery-endpoint--",
KeyVaultEndpoint: "--key-vault--endpoint--",
GraphEndpoint: "--graph-endpoint--",
StorageEndpointSuffix: "--storage-endpoint-suffix--",
SQLDatabaseDNSSuffix: "--sql-database-dns-suffix--",
TrafficManagerDNSSuffix: "--traffic-manager-dns-suffix--",
KeyVaultDNSSuffix: "--key-vault-dns-suffix--",
ServiceBusEndpointSuffix: "--service-bus-endpoint-suffix--",
ServiceManagementVMDNSSuffix: "--asm-vm-dns-suffix--",
ResourceManagerVMDNSSuffix: "--arm-vm-dns-suffix--",
ContainerRegistryDNSSuffix: "--container-registry-dns-suffix--",
}
bytes, err := json.Marshal(env)
if err != nil {
t.Fatalf("failed to marshal: %s", err)
}
testSubject := Environment{}
err = json.Unmarshal(bytes, &testSubject)
if err != nil {
t.Fatalf("failed to unmarshal: %s", err)
}
if env.Name != testSubject.Name {
t.Errorf("Expected Name to be %q, but got %q", env.Name, testSubject.Name)
}
if env.ManagementPortalURL != testSubject.ManagementPortalURL {
t.Errorf("Expected ManagementPortalURL to be %q, but got %q", env.ManagementPortalURL, testSubject.ManagementPortalURL)
}
if env.PublishSettingsURL != testSubject.PublishSettingsURL {
t.Errorf("Expected PublishSettingsURL to be %q, but got %q", env.PublishSettingsURL, testSubject.PublishSettingsURL)
}
if env.ServiceManagementEndpoint != testSubject.ServiceManagementEndpoint {
t.Errorf("Expected ServiceManagementEndpoint to be %q, but got %q", env.ServiceManagementEndpoint, testSubject.ServiceManagementEndpoint)
}
if env.ResourceManagerEndpoint != testSubject.ResourceManagerEndpoint {
t.Errorf("Expected ResourceManagerEndpoint to be %q, but got %q", env.ResourceManagerEndpoint, testSubject.ResourceManagerEndpoint)
}
if env.ActiveDirectoryEndpoint != testSubject.ActiveDirectoryEndpoint {
t.Errorf("Expected ActiveDirectoryEndpoint to be %q, but got %q", env.ActiveDirectoryEndpoint, testSubject.ActiveDirectoryEndpoint)
}
if env.GalleryEndpoint != testSubject.GalleryEndpoint {
t.Errorf("Expected GalleryEndpoint to be %q, but got %q", env.GalleryEndpoint, testSubject.GalleryEndpoint)
}
if env.KeyVaultEndpoint != testSubject.KeyVaultEndpoint {
t.Errorf("Expected KeyVaultEndpoint to be %q, but got %q", env.KeyVaultEndpoint, testSubject.KeyVaultEndpoint)
}
if env.GraphEndpoint != testSubject.GraphEndpoint {
t.Errorf("Expected GraphEndpoint to be %q, but got %q", env.GraphEndpoint, testSubject.GraphEndpoint)
}
if env.StorageEndpointSuffix != testSubject.StorageEndpointSuffix {
t.Errorf("Expected StorageEndpointSuffix to be %q, but got %q", env.StorageEndpointSuffix, testSubject.StorageEndpointSuffix)
}
if env.SQLDatabaseDNSSuffix != testSubject.SQLDatabaseDNSSuffix {
t.Errorf("Expected SQLDatabaseDNSSuffix to be %q, but got %q", env.SQLDatabaseDNSSuffix, testSubject.SQLDatabaseDNSSuffix)
}
if env.TrafficManagerDNSSuffix != testSubject.TrafficManagerDNSSuffix {
t.Errorf("Expected TrafficManagerDNSSuffix to be %q, but got %q", env.TrafficManagerDNSSuffix, testSubject.TrafficManagerDNSSuffix)
}
if env.KeyVaultDNSSuffix != testSubject.KeyVaultDNSSuffix {
t.Errorf("Expected KeyVaultDNSSuffix to be %q, but got %q", env.KeyVaultDNSSuffix, testSubject.KeyVaultDNSSuffix)
}
if env.ServiceBusEndpointSuffix != testSubject.ServiceBusEndpointSuffix {
t.Errorf("Expected ServiceBusEndpointSuffix to be %q, but got %q", env.ServiceBusEndpointSuffix, testSubject.ServiceBusEndpointSuffix)
}
if env.ServiceManagementVMDNSSuffix != testSubject.ServiceManagementVMDNSSuffix {
t.Errorf("Expected ServiceManagementVMDNSSuffix to be %q, but got %q", env.ServiceManagementVMDNSSuffix, testSubject.ServiceManagementVMDNSSuffix)
}
if env.ResourceManagerVMDNSSuffix != testSubject.ResourceManagerVMDNSSuffix {
t.Errorf("Expected ResourceManagerVMDNSSuffix to be %q, but got %q", env.ResourceManagerVMDNSSuffix, testSubject.ResourceManagerVMDNSSuffix)
}
if env.ContainerRegistryDNSSuffix != testSubject.ContainerRegistryDNSSuffix {
t.Errorf("Expected ContainerRegistryDNSSuffix to be %q, but got %q", env.ContainerRegistryDNSSuffix, testSubject.ContainerRegistryDNSSuffix)
}
}

View file

@ -44,7 +44,7 @@ func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator {
return resp, err
}
if resp.StatusCode != http.StatusConflict {
if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration {
return resp, err
}
var re RequestError
@ -159,7 +159,7 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError
}
req.Cancel = originalReq.Cancel
resp, err := autorest.SendWithSender(client.Sender, req,
resp, err := autorest.SendWithSender(client, req,
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
)
if err != nil {

View file

@ -1,81 +0,0 @@
// Copyright 2017 Microsoft Corporation
//
// 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 azure
import (
"net/http"
"testing"
"time"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/mocks"
)
func TestDoRetryWithRegistration(t *testing.T) {
client := mocks.NewSender()
// first response, should retry because it is a transient error
client.AppendResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError))
// response indicates the resource provider has not been registered
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
"error":{
"code":"MissingSubscriptionRegistration",
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions.",
"details":[
{
"code":"MissingSubscriptionRegistration",
"target":"Microsoft.EventGrid",
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions."
}
]
}
}
`), http.StatusConflict, "MissingSubscriptionRegistration"))
// first poll response, still not ready
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
"registrationState": "Registering"
}
`), http.StatusOK, "200 OK"))
// last poll response, respurce provider has been registered
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
"registrationState": "Registered"
}
`), http.StatusOK, "200 OK"))
// retry original request, response is successful
client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
req := mocks.NewRequestForURL("https://lol/subscriptions/rofl")
req.Body = mocks.NewBody("lolol")
r, err := autorest.SendWithSender(client, req,
DoRetryWithRegistration(autorest.Client{
PollingDelay: time.Second,
PollingDuration: time.Second * 10,
RetryAttempts: 5,
RetryDuration: time.Second,
Sender: client,
}),
)
if err != nil {
t.Fatalf("got error: %v", err)
}
autorest.Respond(r,
autorest.ByDiscardingBody(),
autorest.ByClosing(),
)
if r.StatusCode != http.StatusOK {
t.Fatalf("azure: Sender#DoRetryWithRegistration -- Got: StatusCode %v; Want: StatusCode 200 OK", r.StatusCode)
}
}

View file

@ -166,6 +166,9 @@ type Client struct {
UserAgent string
Jar http.CookieJar
// Set to true to skip attempted registration of resource providers (false by default).
SkipResourceProviderRegistration bool
}
// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
@ -204,7 +207,13 @@ func (c Client) Do(r *http.Request) (*http.Response, error) {
c.WithInspection(),
c.WithAuthorization())
if err != nil {
return nil, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
var resp *http.Response
if detErr, ok := err.(DetailedError); ok {
// if the authorization failed (e.g. invalid credentials) there will
// be a response associated with the error, be sure to return it.
resp = detErr.Response
}
return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
}
resp, err := SendWithSender(c.sender(), r)

View file

@ -1,402 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
"github.com/Azure/go-autorest/autorest/mocks"
)
func TestLoggingInspectorWithInspection(t *testing.T) {
b := bytes.Buffer{}
c := Client{}
li := LoggingInspector{Logger: log.New(&b, "", 0)}
c.RequestInspector = li.WithInspection()
Prepare(mocks.NewRequestWithContent("Content"),
c.WithInspection())
if len(b.String()) <= 0 {
t.Fatal("autorest: LoggingInspector#WithInspection did not record Request to the log")
}
}
func TestLoggingInspectorWithInspectionEmitsErrors(t *testing.T) {
b := bytes.Buffer{}
c := Client{}
r := mocks.NewRequestWithContent("Content")
li := LoggingInspector{Logger: log.New(&b, "", 0)}
c.RequestInspector = li.WithInspection()
if _, err := Prepare(r,
c.WithInspection()); err != nil {
t.Error(err)
}
if len(b.String()) <= 0 {
t.Fatal("autorest: LoggingInspector#WithInspection did not record Request to the log")
}
}
func TestLoggingInspectorWithInspectionRestoresBody(t *testing.T) {
b := bytes.Buffer{}
c := Client{}
r := mocks.NewRequestWithContent("Content")
li := LoggingInspector{Logger: log.New(&b, "", 0)}
c.RequestInspector = li.WithInspection()
Prepare(r,
c.WithInspection())
s, _ := ioutil.ReadAll(r.Body)
if len(s) <= 0 {
t.Fatal("autorest: LoggingInspector#WithInspection did not restore the Request body")
}
}
func TestLoggingInspectorByInspecting(t *testing.T) {
b := bytes.Buffer{}
c := Client{}
li := LoggingInspector{Logger: log.New(&b, "", 0)}
c.ResponseInspector = li.ByInspecting()
Respond(mocks.NewResponseWithContent("Content"),
c.ByInspecting())
if len(b.String()) <= 0 {
t.Fatal("autorest: LoggingInspector#ByInspection did not record Response to the log")
}
}
func TestLoggingInspectorByInspectingEmitsErrors(t *testing.T) {
b := bytes.Buffer{}
c := Client{}
r := mocks.NewResponseWithContent("Content")
li := LoggingInspector{Logger: log.New(&b, "", 0)}
c.ResponseInspector = li.ByInspecting()
if err := Respond(r,
c.ByInspecting()); err != nil {
t.Fatal(err)
}
if len(b.String()) <= 0 {
t.Fatal("autorest: LoggingInspector#ByInspection did not record Response to the log")
}
}
func TestLoggingInspectorByInspectingRestoresBody(t *testing.T) {
b := bytes.Buffer{}
c := Client{}
r := mocks.NewResponseWithContent("Content")
li := LoggingInspector{Logger: log.New(&b, "", 0)}
c.ResponseInspector = li.ByInspecting()
Respond(r,
c.ByInspecting())
s, _ := ioutil.ReadAll(r.Body)
if len(s) <= 0 {
t.Fatal("autorest: LoggingInspector#ByInspecting did not restore the Response body")
}
}
func TestNewClientWithUserAgent(t *testing.T) {
ua := "UserAgent"
c := NewClientWithUserAgent(ua)
completeUA := fmt.Sprintf("%s %s", defaultUserAgent, ua)
if c.UserAgent != completeUA {
t.Fatalf("autorest: NewClientWithUserAgent failed to set the UserAgent -- expected %s, received %s",
completeUA, c.UserAgent)
}
}
func TestAddToUserAgent(t *testing.T) {
ua := "UserAgent"
c := NewClientWithUserAgent(ua)
ext := "extension"
err := c.AddToUserAgent(ext)
if err != nil {
t.Fatalf("autorest: AddToUserAgent returned error -- expected nil, received %s", err)
}
completeUA := fmt.Sprintf("%s %s %s", defaultUserAgent, ua, ext)
if c.UserAgent != completeUA {
t.Fatalf("autorest: AddToUserAgent failed to add an extension to the UserAgent -- expected %s, received %s",
completeUA, c.UserAgent)
}
err = c.AddToUserAgent("")
if err == nil {
t.Fatalf("autorest: AddToUserAgent didn't return error -- expected %s, received nil",
fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent))
}
if c.UserAgent != completeUA {
t.Fatalf("autorest: AddToUserAgent failed to not add an empty extension to the UserAgent -- expected %s, received %s",
completeUA, c.UserAgent)
}
}
func TestClientSenderReturnsHttpClientByDefault(t *testing.T) {
c := Client{}
if fmt.Sprintf("%T", c.sender()) != "*http.Client" {
t.Fatal("autorest: Client#sender failed to return http.Client by default")
}
}
func TestClientSenderReturnsSetSender(t *testing.T) {
c := Client{}
s := mocks.NewSender()
c.Sender = s
if c.sender() != s {
t.Fatal("autorest: Client#sender failed to return set Sender")
}
}
func TestClientDoInvokesSender(t *testing.T) {
c := Client{}
s := mocks.NewSender()
c.Sender = s
c.Do(&http.Request{})
if s.Attempts() != 1 {
t.Fatal("autorest: Client#Do failed to invoke the Sender")
}
}
func TestClientDoSetsUserAgent(t *testing.T) {
ua := "UserAgent"
c := Client{UserAgent: ua}
r := mocks.NewRequest()
s := mocks.NewSender()
c.Sender = s
c.Do(r)
if r.UserAgent() != ua {
t.Fatalf("autorest: Client#Do failed to correctly set User-Agent header: %s=%s",
http.CanonicalHeaderKey(headerUserAgent), r.UserAgent())
}
}
func TestClientDoSetsAuthorization(t *testing.T) {
r := mocks.NewRequest()
s := mocks.NewSender()
c := Client{Authorizer: mockAuthorizer{}, Sender: s}
c.Do(r)
if len(r.Header.Get(http.CanonicalHeaderKey(headerAuthorization))) <= 0 {
t.Fatalf("autorest: Client#Send failed to set Authorization header -- %s=%s",
http.CanonicalHeaderKey(headerAuthorization),
r.Header.Get(http.CanonicalHeaderKey(headerAuthorization)))
}
}
func TestClientDoInvokesRequestInspector(t *testing.T) {
r := mocks.NewRequest()
s := mocks.NewSender()
i := &mockInspector{}
c := Client{RequestInspector: i.WithInspection(), Sender: s}
c.Do(r)
if !i.wasInvoked {
t.Fatal("autorest: Client#Send failed to invoke the RequestInspector")
}
}
func TestClientDoInvokesResponseInspector(t *testing.T) {
r := mocks.NewRequest()
s := mocks.NewSender()
i := &mockInspector{}
c := Client{ResponseInspector: i.ByInspecting(), Sender: s}
c.Do(r)
if !i.wasInvoked {
t.Fatal("autorest: Client#Send failed to invoke the ResponseInspector")
}
}
func TestClientDoReturnsErrorIfPrepareFails(t *testing.T) {
c := Client{}
s := mocks.NewSender()
c.Authorizer = mockFailingAuthorizer{}
c.Sender = s
_, err := c.Do(&http.Request{})
if err == nil {
t.Fatalf("autorest: Client#Do failed to return an error when Prepare failed")
}
}
func TestClientDoDoesNotSendIfPrepareFails(t *testing.T) {
c := Client{}
s := mocks.NewSender()
c.Authorizer = mockFailingAuthorizer{}
c.Sender = s
c.Do(&http.Request{})
if s.Attempts() > 0 {
t.Fatal("autorest: Client#Do failed to invoke the Sender")
}
}
func TestClientAuthorizerReturnsNullAuthorizerByDefault(t *testing.T) {
c := Client{}
if fmt.Sprintf("%T", c.authorizer()) != "autorest.NullAuthorizer" {
t.Fatal("autorest: Client#authorizer failed to return the NullAuthorizer by default")
}
}
func TestClientAuthorizerReturnsSetAuthorizer(t *testing.T) {
c := Client{}
c.Authorizer = mockAuthorizer{}
if fmt.Sprintf("%T", c.authorizer()) != "autorest.mockAuthorizer" {
t.Fatal("autorest: Client#authorizer failed to return the set Authorizer")
}
}
func TestClientWithAuthorizer(t *testing.T) {
c := Client{}
c.Authorizer = mockAuthorizer{}
req, _ := Prepare(&http.Request{},
c.WithAuthorization())
if req.Header.Get(headerAuthorization) == "" {
t.Fatal("autorest: Client#WithAuthorizer failed to return the WithAuthorizer from the active Authorizer")
}
}
func TestClientWithInspection(t *testing.T) {
c := Client{}
r := &mockInspector{}
c.RequestInspector = r.WithInspection()
Prepare(&http.Request{},
c.WithInspection())
if !r.wasInvoked {
t.Fatal("autorest: Client#WithInspection failed to invoke RequestInspector")
}
}
func TestClientWithInspectionSetsDefault(t *testing.T) {
c := Client{}
r1 := &http.Request{}
r2, _ := Prepare(r1,
c.WithInspection())
if !reflect.DeepEqual(r1, r2) {
t.Fatal("autorest: Client#WithInspection failed to provide a default RequestInspector")
}
}
func TestClientByInspecting(t *testing.T) {
c := Client{}
r := &mockInspector{}
c.ResponseInspector = r.ByInspecting()
Respond(&http.Response{},
c.ByInspecting())
if !r.wasInvoked {
t.Fatal("autorest: Client#ByInspecting failed to invoke ResponseInspector")
}
}
func TestClientByInspectingSetsDefault(t *testing.T) {
c := Client{}
r := &http.Response{}
Respond(r,
c.ByInspecting())
if !reflect.DeepEqual(r, &http.Response{}) {
t.Fatal("autorest: Client#ByInspecting failed to provide a default ResponseInspector")
}
}
func TestCookies(t *testing.T) {
second := "second"
expected := http.Cookie{
Name: "tastes",
Value: "delicious",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &expected)
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: ioutil.ReadAll failed reading request body: %s", err)
}
if string(b) == second {
cookie, err := r.Cookie(expected.Name)
if err != nil {
t.Fatalf("autorest: r.Cookie could not get request cookie: %s", err)
}
if cookie == nil {
t.Fatalf("autorest: got nil cookie, expecting %v", expected)
}
if cookie.Value != expected.Value {
t.Fatalf("autorest: got cookie value '%s', expecting '%s'", cookie.Value, expected.Name)
}
}
}))
defer server.Close()
client := NewClientWithUserAgent("")
_, err := SendWithSender(client, mocks.NewRequestForURL(server.URL))
if err != nil {
t.Fatalf("autorest: first request failed: %s", err)
}
r2, err := http.NewRequest(http.MethodGet, server.URL, mocks.NewBody(second))
if err != nil {
t.Fatalf("autorest: failed creating second request: %s", err)
}
_, err = SendWithSender(client, r2)
if err != nil {
t.Fatalf("autorest: second request failed: %s", err)
}
}
func randomString(n int) string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
s := make([]byte, n)
for i := range s {
s[i] = chars[r.Intn(len(chars))]
}
return string(s)
}

View file

@ -1,237 +0,0 @@
package date
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
)
func ExampleParseDate() {
d, err := ParseDate("2001-02-03")
if err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: 2001-02-03
}
func ExampleDate() {
d, err := ParseDate("2001-02-03")
if err != nil {
fmt.Println(err)
}
t, err := time.Parse(time.RFC3339, "2001-02-04T00:00:00Z")
if err != nil {
fmt.Println(err)
}
// Date acts as time.Time when the receiver
if d.Before(t) {
fmt.Printf("Before ")
} else {
fmt.Printf("After ")
}
// Convert Date when needing a time.Time
if t.After(d.ToTime()) {
fmt.Printf("After")
} else {
fmt.Printf("Before")
}
// Output: Before After
}
func ExampleDate_MarshalBinary() {
d, err := ParseDate("2001-02-03")
if err != nil {
fmt.Println(err)
}
t, err := d.MarshalBinary()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(t))
// Output: 2001-02-03
}
func ExampleDate_UnmarshalBinary() {
d := Date{}
t := "2001-02-03"
if err := d.UnmarshalBinary([]byte(t)); err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: 2001-02-03
}
func ExampleDate_MarshalJSON() {
d, err := ParseDate("2001-02-03")
if err != nil {
fmt.Println(err)
}
j, err := json.Marshal(d)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(j))
// Output: "2001-02-03"
}
func ExampleDate_UnmarshalJSON() {
var d struct {
Date Date `json:"date"`
}
j := `{"date" : "2001-02-03"}`
if err := json.Unmarshal([]byte(j), &d); err != nil {
fmt.Println(err)
}
fmt.Println(d.Date)
// Output: 2001-02-03
}
func ExampleDate_MarshalText() {
d, err := ParseDate("2001-02-03")
if err != nil {
fmt.Println(err)
}
t, err := d.MarshalText()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(t))
// Output: 2001-02-03
}
func ExampleDate_UnmarshalText() {
d := Date{}
t := "2001-02-03"
if err := d.UnmarshalText([]byte(t)); err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: 2001-02-03
}
func TestDateString(t *testing.T) {
d, err := ParseDate("2001-02-03")
if err != nil {
t.Fatalf("date: String failed (%v)", err)
}
if d.String() != "2001-02-03" {
t.Fatalf("date: String failed (%v)", d.String())
}
}
func TestDateBinaryRoundTrip(t *testing.T) {
d1, err := ParseDate("2001-02-03")
if err != nil {
t.Fatalf("date: ParseDate failed (%v)", err)
}
t1, err := d1.MarshalBinary()
if err != nil {
t.Fatalf("date: MarshalBinary failed (%v)", err)
}
d2 := Date{}
if err = d2.UnmarshalBinary(t1); err != nil {
t.Fatalf("date: UnmarshalBinary failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip Binary failed (%v, %v)", d1, d2)
}
}
func TestDateJSONRoundTrip(t *testing.T) {
type s struct {
Date Date `json:"date"`
}
var err error
d1 := s{}
d1.Date, err = ParseDate("2001-02-03")
if err != nil {
t.Fatalf("date: ParseDate failed (%v)", err)
}
j, err := json.Marshal(d1)
if err != nil {
t.Fatalf("date: MarshalJSON failed (%v)", err)
}
d2 := s{}
if err = json.Unmarshal(j, &d2); err != nil {
t.Fatalf("date: UnmarshalJSON failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip JSON failed (%v, %v)", d1, d2)
}
}
func TestDateTextRoundTrip(t *testing.T) {
d1, err := ParseDate("2001-02-03")
if err != nil {
t.Fatalf("date: ParseDate failed (%v)", err)
}
t1, err := d1.MarshalText()
if err != nil {
t.Fatalf("date: MarshalText failed (%v)", err)
}
d2 := Date{}
if err = d2.UnmarshalText(t1); err != nil {
t.Fatalf("date: UnmarshalText failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip Text failed (%v, %v)", d1, d2)
}
}
func TestDateToTime(t *testing.T) {
var d Date
d, err := ParseDate("2001-02-03")
if err != nil {
t.Fatalf("date: ParseDate failed (%v)", err)
}
var _ time.Time = d.ToTime()
}
func TestDateUnmarshalJSONReturnsError(t *testing.T) {
var d struct {
Date Date `json:"date"`
}
j := `{"date" : "February 3, 2001"}`
if err := json.Unmarshal([]byte(j), &d); err == nil {
t.Fatal("date: Date failed to return error for malformed JSON date")
}
}
func TestDateUnmarshalTextReturnsError(t *testing.T) {
d := Date{}
txt := "February 3, 2001"
if err := d.UnmarshalText([]byte(txt)); err == nil {
t.Fatal("date: Date failed to return error for malformed Text date")
}
}

View file

@ -1,277 +0,0 @@
package date
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
)
func ExampleParseTime() {
d, _ := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
fmt.Println(d)
// Output: 2001-02-03 04:05:06 +0000 UTC
}
func ExampleTime_MarshalBinary() {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
fmt.Println(err)
}
d := Time{ti}
t, err := d.MarshalBinary()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(t))
// Output: 2001-02-03T04:05:06Z
}
func ExampleTime_UnmarshalBinary() {
d := Time{}
t := "2001-02-03T04:05:06Z"
if err := d.UnmarshalBinary([]byte(t)); err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: 2001-02-03T04:05:06Z
}
func ExampleTime_MarshalJSON() {
d, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
fmt.Println(err)
}
j, err := json.Marshal(d)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(j))
// Output: "2001-02-03T04:05:06Z"
}
func ExampleTime_UnmarshalJSON() {
var d struct {
Time Time `json:"datetime"`
}
j := `{"datetime" : "2001-02-03T04:05:06Z"}`
if err := json.Unmarshal([]byte(j), &d); err != nil {
fmt.Println(err)
}
fmt.Println(d.Time)
// Output: 2001-02-03T04:05:06Z
}
func ExampleTime_MarshalText() {
d, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
fmt.Println(err)
}
t, err := d.MarshalText()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(t))
// Output: 2001-02-03T04:05:06Z
}
func ExampleTime_UnmarshalText() {
d := Time{}
t := "2001-02-03T04:05:06Z"
if err := d.UnmarshalText([]byte(t)); err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: 2001-02-03T04:05:06Z
}
func TestUnmarshalTextforInvalidDate(t *testing.T) {
d := Time{}
dt := "2001-02-03T04:05:06AAA"
if err := d.UnmarshalText([]byte(dt)); err == nil {
t.Fatalf("date: Time#Unmarshal was expecting error for invalid date")
}
}
func TestUnmarshalJSONforInvalidDate(t *testing.T) {
d := Time{}
dt := `"2001-02-03T04:05:06AAA"`
if err := d.UnmarshalJSON([]byte(dt)); err == nil {
t.Fatalf("date: Time#Unmarshal was expecting error for invalid date")
}
}
func TestTimeString(t *testing.T) {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
fmt.Println(err)
}
d := Time{ti}
if d.String() != "2001-02-03T04:05:06Z" {
t.Fatalf("date: Time#String failed (%v)", d.String())
}
}
func TestTimeStringReturnsEmptyStringForError(t *testing.T) {
d := Time{Time: time.Date(20000, 01, 01, 01, 01, 01, 01, time.UTC)}
if d.String() != "" {
t.Fatalf("date: Time#String failed empty string for an error")
}
}
func TestTimeBinaryRoundTrip(t *testing.T) {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
t.Fatalf("date: Time#ParseTime failed (%v)", err)
}
d1 := Time{ti}
t1, err := d1.MarshalBinary()
if err != nil {
t.Fatalf("date: Time#MarshalBinary failed (%v)", err)
}
d2 := Time{}
if err = d2.UnmarshalBinary(t1); err != nil {
t.Fatalf("date: Time#UnmarshalBinary failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date:Round-trip Binary failed (%v, %v)", d1, d2)
}
}
func TestTimeJSONRoundTrip(t *testing.T) {
type s struct {
Time Time `json:"datetime"`
}
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
t.Fatalf("date: Time#ParseTime failed (%v)", err)
}
d1 := s{Time: Time{ti}}
j, err := json.Marshal(d1)
if err != nil {
t.Fatalf("date: Time#MarshalJSON failed (%v)", err)
}
d2 := s{}
if err = json.Unmarshal(j, &d2); err != nil {
t.Fatalf("date: Time#UnmarshalJSON failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip JSON failed (%v, %v)", d1, d2)
}
}
func TestTimeTextRoundTrip(t *testing.T) {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
t.Fatalf("date: Time#ParseTime failed (%v)", err)
}
d1 := Time{Time: ti}
t1, err := d1.MarshalText()
if err != nil {
t.Fatalf("date: Time#MarshalText failed (%v)", err)
}
d2 := Time{}
if err = d2.UnmarshalText(t1); err != nil {
t.Fatalf("date: Time#UnmarshalText failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip Text failed (%v, %v)", d1, d2)
}
}
func TestTimeToTime(t *testing.T) {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
d := Time{ti}
if err != nil {
t.Fatalf("date: Time#ParseTime failed (%v)", err)
}
var _ time.Time = d.ToTime()
}
func TestUnmarshalJSONNoOffset(t *testing.T) {
var d struct {
Time Time `json:"datetime"`
}
j := `{"datetime" : "2001-02-03T04:05:06.789"}`
if err := json.Unmarshal([]byte(j), &d); err != nil {
t.Fatalf("date: Time#Unmarshal failed (%v)", err)
}
}
func TestUnmarshalJSONPosOffset(t *testing.T) {
var d struct {
Time Time `json:"datetime"`
}
j := `{"datetime" : "1980-01-02T00:11:35.01+01:00"}`
if err := json.Unmarshal([]byte(j), &d); err != nil {
t.Fatalf("date: Time#Unmarshal failed (%v)", err)
}
}
func TestUnmarshalJSONNegOffset(t *testing.T) {
var d struct {
Time Time `json:"datetime"`
}
j := `{"datetime" : "1492-10-12T10:15:01.789-08:00"}`
if err := json.Unmarshal([]byte(j), &d); err != nil {
t.Fatalf("date: Time#Unmarshal failed (%v)", err)
}
}
func TestUnmarshalTextNoOffset(t *testing.T) {
d := Time{}
t1 := "2001-02-03T04:05:06"
if err := d.UnmarshalText([]byte(t1)); err != nil {
t.Fatalf("date: Time#UnmarshalText failed (%v)", err)
}
}
func TestUnmarshalTextPosOffset(t *testing.T) {
d := Time{}
t1 := "2001-02-03T04:05:06+00:30"
if err := d.UnmarshalText([]byte(t1)); err != nil {
t.Fatalf("date: Time#UnmarshalText failed (%v)", err)
}
}
func TestUnmarshalTextNegOffset(t *testing.T) {
d := Time{}
t1 := "2001-02-03T04:05:06-11:00"
if err := d.UnmarshalText([]byte(t1)); err != nil {
t.Fatalf("date: Time#UnmarshalText failed (%v)", err)
}
}

View file

@ -1,226 +0,0 @@
package date
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
)
func ExampleTimeRFC1123() {
d, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
if err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: 2006-01-02 15:04:05 +0000 MST
}
func ExampleTimeRFC1123_MarshalBinary() {
ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
if err != nil {
fmt.Println(err)
}
d := TimeRFC1123{ti}
b, err := d.MarshalBinary()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(b))
// Output: Mon, 02 Jan 2006 15:04:05 MST
}
func ExampleTimeRFC1123_UnmarshalBinary() {
d := TimeRFC1123{}
t := "Mon, 02 Jan 2006 15:04:05 MST"
if err := d.UnmarshalBinary([]byte(t)); err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: Mon, 02 Jan 2006 15:04:05 MST
}
func ExampleTimeRFC1123_MarshalJSON() {
ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
if err != nil {
fmt.Println(err)
}
d := TimeRFC1123{ti}
j, err := json.Marshal(d)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(j))
// Output: "Mon, 02 Jan 2006 15:04:05 MST"
}
func TestTimeRFC1123MarshalJSONInvalid(t *testing.T) {
ti := time.Date(20000, 01, 01, 00, 00, 00, 00, time.UTC)
d := TimeRFC1123{ti}
if _, err := json.Marshal(d); err == nil {
t.Fatalf("date: TimeRFC1123#Marshal failed for invalid date")
}
}
func ExampleTimeRFC1123_UnmarshalJSON() {
var d struct {
Time TimeRFC1123 `json:"datetime"`
}
j := `{"datetime" : "Mon, 02 Jan 2006 15:04:05 MST"}`
if err := json.Unmarshal([]byte(j), &d); err != nil {
fmt.Println(err)
}
fmt.Println(d.Time)
// Output: Mon, 02 Jan 2006 15:04:05 MST
}
func ExampleTimeRFC1123_MarshalText() {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
fmt.Println(err)
}
d := TimeRFC1123{ti}
t, err := d.MarshalText()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(t))
// Output: Sat, 03 Feb 2001 04:05:06 UTC
}
func ExampleTimeRFC1123_UnmarshalText() {
d := TimeRFC1123{}
t := "Sat, 03 Feb 2001 04:05:06 UTC"
if err := d.UnmarshalText([]byte(t)); err != nil {
fmt.Println(err)
}
fmt.Println(d)
// Output: Sat, 03 Feb 2001 04:05:06 UTC
}
func TestUnmarshalJSONforInvalidDateRfc1123(t *testing.T) {
dt := `"Mon, 02 Jan 2000000 15:05 MST"`
d := TimeRFC1123{}
if err := d.UnmarshalJSON([]byte(dt)); err == nil {
t.Fatalf("date: TimeRFC1123#Unmarshal failed for invalid date")
}
}
func TestUnmarshalTextforInvalidDateRfc1123(t *testing.T) {
dt := "Mon, 02 Jan 2000000 15:05 MST"
d := TimeRFC1123{}
if err := d.UnmarshalText([]byte(dt)); err == nil {
t.Fatalf("date: TimeRFC1123#Unmarshal failed for invalid date")
}
}
func TestTimeStringRfc1123(t *testing.T) {
ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
if err != nil {
fmt.Println(err)
}
d := TimeRFC1123{ti}
if d.String() != "Mon, 02 Jan 2006 15:04:05 MST" {
t.Fatalf("date: TimeRFC1123#String failed (%v)", d.String())
}
}
func TestTimeStringReturnsEmptyStringForErrorRfc1123(t *testing.T) {
d := TimeRFC1123{Time: time.Date(20000, 01, 01, 01, 01, 01, 01, time.UTC)}
if d.String() != "" {
t.Fatalf("date: TimeRFC1123#String failed empty string for an error")
}
}
func TestTimeBinaryRoundTripRfc1123(t *testing.T) {
ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z")
if err != nil {
t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err)
}
d1 := TimeRFC1123{ti}
t1, err := d1.MarshalBinary()
if err != nil {
t.Fatalf("date: TimeRFC1123#MarshalBinary failed (%v)", err)
}
d2 := TimeRFC1123{}
if err = d2.UnmarshalBinary(t1); err != nil {
t.Fatalf("date: TimeRFC1123#UnmarshalBinary failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip Binary failed (%v, %v)", d1, d2)
}
}
func TestTimeJSONRoundTripRfc1123(t *testing.T) {
type s struct {
Time TimeRFC1123 `json:"datetime"`
}
var err error
ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
if err != nil {
t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err)
}
d1 := s{Time: TimeRFC1123{ti}}
j, err := json.Marshal(d1)
if err != nil {
t.Fatalf("date: TimeRFC1123#MarshalJSON failed (%v)", err)
}
d2 := s{}
if err = json.Unmarshal(j, &d2); err != nil {
t.Fatalf("date: TimeRFC1123#UnmarshalJSON failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip JSON failed (%v, %v)", d1, d2)
}
}
func TestTimeTextRoundTripRfc1123(t *testing.T) {
ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
if err != nil {
t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err)
}
d1 := TimeRFC1123{Time: ti}
t1, err := d1.MarshalText()
if err != nil {
t.Fatalf("date: TimeRFC1123#MarshalText failed (%v)", err)
}
d2 := TimeRFC1123{}
if err = d2.UnmarshalText(t1); err != nil {
t.Fatalf("date: TimeRFC1123#UnmarshalText failed (%v)", err)
}
if !reflect.DeepEqual(d1, d2) {
t.Fatalf("date: Round-trip Text failed (%v, %v)", d1, d2)
}
}
func TestTimeToTimeRFC1123(t *testing.T) {
ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST")
d := TimeRFC1123{ti}
if err != nil {
t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err)
}
var _ time.Time = d.ToTime()
}

View file

@ -1,283 +0,0 @@
// +build go1.7
package date
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"math"
"testing"
"time"
)
func ExampleUnixTime_MarshalJSON() {
epoch := UnixTime(UnixEpoch())
text, _ := json.Marshal(epoch)
fmt.Print(string(text))
// Output: 0
}
func ExampleUnixTime_UnmarshalJSON() {
var myTime UnixTime
json.Unmarshal([]byte("1.3e2"), &myTime)
fmt.Printf("%v", time.Time(myTime))
// Output: 1970-01-01 00:02:10 +0000 UTC
}
func TestUnixTime_MarshalJSON(t *testing.T) {
testCases := []time.Time{
UnixEpoch().Add(-1 * time.Second), // One second befote the Unix Epoch
time.Date(2017, time.April, 14, 20, 27, 47, 0, time.UTC), // The time this test was written
UnixEpoch(),
time.Date(1800, 01, 01, 0, 0, 0, 0, time.UTC),
time.Date(2200, 12, 29, 00, 01, 37, 82, time.UTC),
}
for _, tc := range testCases {
t.Run(tc.String(), func(subT *testing.T) {
var actual, expected float64
var marshaled []byte
target := UnixTime(tc)
expected = float64(target.Duration().Nanoseconds()) / 1e9
if temp, err := json.Marshal(target); err == nil {
marshaled = temp
} else {
subT.Error(err)
return
}
dec := json.NewDecoder(bytes.NewReader(marshaled))
if err := dec.Decode(&actual); err != nil {
subT.Error(err)
return
}
diff := math.Abs(actual - expected)
subT.Logf("\ngot :\t%g\nwant:\t%g\ndiff:\t%g", actual, expected, diff)
if diff > 1e-9 { //Must be within 1 nanosecond of one another
subT.Fail()
}
})
}
}
func TestUnixTime_UnmarshalJSON(t *testing.T) {
testCases := []struct {
text string
expected time.Time
}{
{"1", UnixEpoch().Add(time.Second)},
{"0", UnixEpoch()},
{"1492203742", time.Date(2017, time.April, 14, 21, 02, 22, 0, time.UTC)}, // The time this test was written
{"-1", time.Date(1969, time.December, 31, 23, 59, 59, 0, time.UTC)},
{"1.5", UnixEpoch().Add(1500 * time.Millisecond)},
{"0e1", UnixEpoch()}, // See http://json.org for 'number' format definition.
{"1.3e+2", UnixEpoch().Add(130 * time.Second)},
{"1.6E-10", UnixEpoch()}, // This is so small, it should get truncated into the UnixEpoch
{"2E-6", UnixEpoch().Add(2 * time.Microsecond)},
{"1.289345e9", UnixEpoch().Add(1289345000 * time.Second)},
{"1e-9", UnixEpoch().Add(time.Nanosecond)},
}
for _, tc := range testCases {
t.Run(tc.text, func(subT *testing.T) {
var rehydrated UnixTime
if err := json.Unmarshal([]byte(tc.text), &rehydrated); err != nil {
subT.Error(err)
return
}
if time.Time(rehydrated) != tc.expected {
subT.Logf("\ngot: \t%v\nwant:\t%v\ndiff:\t%v", time.Time(rehydrated), tc.expected, time.Time(rehydrated).Sub(tc.expected))
subT.Fail()
}
})
}
}
func TestUnixTime_JSONRoundTrip(t *testing.T) {
testCases := []time.Time{
UnixEpoch(),
time.Date(2005, time.November, 5, 0, 0, 0, 0, time.UTC), // The day V for Vendetta (film) was released.
UnixEpoch().Add(-6 * time.Second),
UnixEpoch().Add(800 * time.Hour),
UnixEpoch().Add(time.Nanosecond),
time.Date(2015, time.September, 05, 4, 30, 12, 9992, time.UTC),
}
for _, tc := range testCases {
t.Run(tc.String(), func(subT *testing.T) {
subject := UnixTime(tc)
var marshaled []byte
if temp, err := json.Marshal(subject); err == nil {
marshaled = temp
} else {
subT.Error(err)
return
}
var unmarshaled UnixTime
if err := json.Unmarshal(marshaled, &unmarshaled); err != nil {
subT.Error(err)
}
actual := time.Time(unmarshaled)
diff := actual.Sub(tc)
subT.Logf("\ngot :\t%s\nwant:\t%s\ndiff:\t%s", actual.String(), tc.String(), diff.String())
if diff > time.Duration(100) { // We lose some precision be working in floats. We shouldn't lose more than 100 nanoseconds.
subT.Fail()
}
})
}
}
func TestUnixTime_MarshalBinary(t *testing.T) {
testCases := []struct {
expected int64
subject time.Time
}{
{0, UnixEpoch()},
{-15 * int64(time.Second), UnixEpoch().Add(-15 * time.Second)},
{54, UnixEpoch().Add(54 * time.Nanosecond)},
}
for _, tc := range testCases {
t.Run("", func(subT *testing.T) {
var marshaled []byte
if temp, err := UnixTime(tc.subject).MarshalBinary(); err == nil {
marshaled = temp
} else {
subT.Error(err)
return
}
var unmarshaled int64
if err := binary.Read(bytes.NewReader(marshaled), binary.LittleEndian, &unmarshaled); err != nil {
subT.Error(err)
return
}
if unmarshaled != tc.expected {
subT.Logf("\ngot: \t%d\nwant:\t%d", unmarshaled, tc.expected)
subT.Fail()
}
})
}
}
func TestUnixTime_BinaryRoundTrip(t *testing.T) {
testCases := []time.Time{
UnixEpoch(),
UnixEpoch().Add(800 * time.Minute),
UnixEpoch().Add(7 * time.Hour),
UnixEpoch().Add(-1 * time.Nanosecond),
}
for _, tc := range testCases {
t.Run(tc.String(), func(subT *testing.T) {
original := UnixTime(tc)
var marshaled []byte
if temp, err := original.MarshalBinary(); err == nil {
marshaled = temp
} else {
subT.Error(err)
return
}
var traveled UnixTime
if err := traveled.UnmarshalBinary(marshaled); err != nil {
subT.Error(err)
return
}
if traveled != original {
subT.Logf("\ngot: \t%s\nwant:\t%s", time.Time(original).String(), time.Time(traveled).String())
subT.Fail()
}
})
}
}
func TestUnixTime_MarshalText(t *testing.T) {
testCases := []time.Time{
UnixEpoch(),
UnixEpoch().Add(45 * time.Second),
UnixEpoch().Add(time.Nanosecond),
UnixEpoch().Add(-100000 * time.Second),
}
for _, tc := range testCases {
expected, _ := tc.MarshalText()
t.Run("", func(subT *testing.T) {
var marshaled []byte
if temp, err := UnixTime(tc).MarshalText(); err == nil {
marshaled = temp
} else {
subT.Error(err)
return
}
if string(marshaled) != string(expected) {
subT.Logf("\ngot: \t%s\nwant:\t%s", string(marshaled), string(expected))
subT.Fail()
}
})
}
}
func TestUnixTime_TextRoundTrip(t *testing.T) {
testCases := []time.Time{
UnixEpoch(),
UnixEpoch().Add(-1 * time.Nanosecond),
UnixEpoch().Add(1 * time.Nanosecond),
time.Date(2017, time.April, 17, 21, 00, 00, 00, time.UTC),
}
for _, tc := range testCases {
t.Run(tc.String(), func(subT *testing.T) {
unixTC := UnixTime(tc)
var marshaled []byte
if temp, err := unixTC.MarshalText(); err == nil {
marshaled = temp
} else {
subT.Error(err)
return
}
var unmarshaled UnixTime
if err := unmarshaled.UnmarshalText(marshaled); err != nil {
subT.Error(err)
return
}
if unmarshaled != unixTC {
t.Logf("\ngot: \t%s\nwant:\t%s", time.Time(unmarshaled).String(), tc.String())
t.Fail()
}
})
}
}

View file

@ -1,202 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"fmt"
"net/http"
"reflect"
"regexp"
"testing"
)
func TestNewErrorWithError_AssignsPackageType(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if e.PackageType != "packageType" {
t.Fatalf("autorest: Error failed to set package type -- expected %v, received %v", "packageType", e.PackageType)
}
}
func TestNewErrorWithError_AssignsMethod(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if e.Method != "method" {
t.Fatalf("autorest: Error failed to set method -- expected %v, received %v", "method", e.Method)
}
}
func TestNewErrorWithError_AssignsMessage(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if e.Message != "message" {
t.Fatalf("autorest: Error failed to set message -- expected %v, received %v", "message", e.Message)
}
}
func TestNewErrorWithError_AssignsUndefinedStatusCodeIfRespNil(t *testing.T) {
e := NewErrorWithError(nil, "packageType", "method", nil, "message")
if e.StatusCode != UndefinedStatusCode {
t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", UndefinedStatusCode, e.StatusCode)
}
}
func TestNewErrorWithError_AssignsStatusCode(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", &http.Response{
StatusCode: http.StatusBadRequest,
Status: http.StatusText(http.StatusBadRequest)}, "message")
if e.StatusCode != http.StatusBadRequest {
t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", http.StatusBadRequest, e.StatusCode)
}
}
func TestNewErrorWithError_AcceptsArgs(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message %s", "arg")
if matched, _ := regexp.MatchString(`.*arg.*`, e.Message); !matched {
t.Fatalf("autorest: Error failed to apply message arguments -- expected %v, received %v",
`.*arg.*`, e.Message)
}
}
func TestNewErrorWithError_AssignsError(t *testing.T) {
err := fmt.Errorf("original")
e := NewErrorWithError(err, "packageType", "method", nil, "message")
if e.Original != err {
t.Fatalf("autorest: Error failed to set error -- expected %v, received %v", err, e.Original)
}
}
func TestNewErrorWithResponse_ContainsStatusCode(t *testing.T) {
e := NewErrorWithResponse("packageType", "method", &http.Response{
StatusCode: http.StatusBadRequest,
Status: http.StatusText(http.StatusBadRequest)}, "message")
if e.StatusCode != http.StatusBadRequest {
t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", http.StatusBadRequest, e.StatusCode)
}
}
func TestNewErrorWithResponse_nilResponse_ReportsUndefinedStatusCode(t *testing.T) {
e := NewErrorWithResponse("packageType", "method", nil, "message")
if e.StatusCode != UndefinedStatusCode {
t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", UndefinedStatusCode, e.StatusCode)
}
}
func TestNewErrorWithResponse_Forwards(t *testing.T) {
e1 := NewError("packageType", "method", "message %s", "arg")
e2 := NewErrorWithResponse("packageType", "method", nil, "message %s", "arg")
if !reflect.DeepEqual(e1, e2) {
t.Fatal("autorest: NewError did not return an error equivelent to NewErrorWithError")
}
}
func TestNewErrorWithError_Forwards(t *testing.T) {
e1 := NewError("packageType", "method", "message %s", "arg")
e2 := NewErrorWithError(nil, "packageType", "method", nil, "message %s", "arg")
if !reflect.DeepEqual(e1, e2) {
t.Fatal("autorest: NewError did not return an error equivelent to NewErrorWithError")
}
}
func TestNewErrorWithError_DoesNotWrapADetailedError(t *testing.T) {
e1 := NewError("packageType1", "method1", "message1 %s", "arg1")
e2 := NewErrorWithError(e1, "packageType2", "method2", nil, "message2 %s", "arg2")
if !reflect.DeepEqual(e1, e2) {
t.Fatalf("autorest: NewErrorWithError incorrectly wrapped a DetailedError -- expected %v, received %v", e1, e2)
}
}
func TestNewErrorWithError_WrapsAnError(t *testing.T) {
e1 := fmt.Errorf("Inner Error")
var e2 interface{} = NewErrorWithError(e1, "packageType", "method", nil, "message")
if _, ok := e2.(DetailedError); !ok {
t.Fatalf("autorest: NewErrorWithError failed to wrap a standard error -- received %T", e2)
}
}
func TestDetailedError(t *testing.T) {
err := fmt.Errorf("original")
e := NewErrorWithError(err, "packageType", "method", nil, "message")
if matched, _ := regexp.MatchString(`.*original.*`, e.Error()); !matched {
t.Fatalf("autorest: Error#Error failed to return original error message -- expected %v, received %v",
`.*original.*`, e.Error())
}
}
func TestDetailedErrorConstainsPackageType(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if matched, _ := regexp.MatchString(`.*packageType.*`, e.Error()); !matched {
t.Fatalf("autorest: Error#String failed to include PackageType -- expected %v, received %v",
`.*packageType.*`, e.Error())
}
}
func TestDetailedErrorConstainsMethod(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if matched, _ := regexp.MatchString(`.*method.*`, e.Error()); !matched {
t.Fatalf("autorest: Error#String failed to include Method -- expected %v, received %v",
`.*method.*`, e.Error())
}
}
func TestDetailedErrorConstainsMessage(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if matched, _ := regexp.MatchString(`.*message.*`, e.Error()); !matched {
t.Fatalf("autorest: Error#String failed to include Message -- expected %v, received %v",
`.*message.*`, e.Error())
}
}
func TestDetailedErrorConstainsStatusCode(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", &http.Response{
StatusCode: http.StatusBadRequest,
Status: http.StatusText(http.StatusBadRequest)}, "message")
if matched, _ := regexp.MatchString(`.*400.*`, e.Error()); !matched {
t.Fatalf("autorest: Error#String failed to include Status Code -- expected %v, received %v",
`.*400.*`, e.Error())
}
}
func TestDetailedErrorConstainsOriginal(t *testing.T) {
e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message")
if matched, _ := regexp.MatchString(`.*original.*`, e.Error()); !matched {
t.Fatalf("autorest: Error#String failed to include Original error -- expected %v, received %v",
`.*original.*`, e.Error())
}
}
func TestDetailedErrorSkipsOriginal(t *testing.T) {
e := NewError("packageType", "method", "message")
if matched, _ := regexp.MatchString(`.*Original.*`, e.Error()); matched {
t.Fatalf("autorest: Error#String included missing Original error -- unexpected %v, received %v",
`.*Original.*`, e.Error())
}
}

View file

@ -27,8 +27,9 @@ import (
)
const (
mimeTypeJSON = "application/json"
mimeTypeFormPost = "application/x-www-form-urlencoded"
mimeTypeJSON = "application/json"
mimeTypeOctetStream = "application/octet-stream"
mimeTypeFormPost = "application/x-www-form-urlencoded"
headerAuthorization = "Authorization"
headerContentType = "Content-Type"
@ -112,6 +113,28 @@ func WithHeader(header string, value string) PrepareDecorator {
}
}
// WithHeaders returns a PrepareDecorator that sets the specified HTTP headers of the http.Request to
// the passed value. It canonicalizes the passed headers name (via http.CanonicalHeaderKey) before
// adding them.
func WithHeaders(headers map[string]interface{}) PrepareDecorator {
h := ensureValueStrings(headers)
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
r, err := p.Prepare(r)
if err == nil {
if r.Header == nil {
r.Header = make(http.Header)
}
for name, value := range h {
r.Header.Set(http.CanonicalHeaderKey(name), value)
}
}
return r, err
})
}
}
// WithBearerAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
// value is "Bearer " followed by the supplied token.
func WithBearerAuthorization(token string) PrepareDecorator {
@ -142,6 +165,11 @@ func AsJSON() PrepareDecorator {
return AsContentType(mimeTypeJSON)
}
// AsOctetStream returns a PrepareDecorator that adds the "application/octet-stream" Content-Type header.
func AsOctetStream() PrepareDecorator {
return AsContentType(mimeTypeOctetStream)
}
// WithMethod returns a PrepareDecorator that sets the HTTP method of the passed request. The
// decorator does not validate that the passed method string is a known HTTP method.
func WithMethod(method string) PrepareDecorator {
@ -215,6 +243,11 @@ func WithFormData(v url.Values) PrepareDecorator {
r, err := p.Prepare(r)
if err == nil {
s := v.Encode()
if r.Header == nil {
r.Header = make(http.Header)
}
r.Header.Set(http.CanonicalHeaderKey(headerContentType), mimeTypeFormPost)
r.ContentLength = int64(len(s))
r.Body = ioutil.NopCloser(strings.NewReader(s))
}
@ -430,11 +463,16 @@ func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorato
if r.URL == nil {
return r, NewError("autorest", "WithQueryParameters", "Invoked with a nil URL")
}
v := r.URL.Query()
for key, value := range parameters {
v.Add(key, value)
d, err := url.QueryUnescape(value)
if err != nil {
return r, err
}
v.Add(key, d)
}
r.URL.RawQuery = createQuery(v)
r.URL.RawQuery = v.Encode()
}
return r, err
})

View file

@ -1,766 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"testing"
"github.com/Azure/go-autorest/autorest/mocks"
)
// PrepareDecorators wrap and invoke a Preparer. Most often, the decorator invokes the passed
// Preparer and decorates the response.
func ExamplePrepareDecorator() {
path := "a/b/c/"
pd := func() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
r, err := p.Prepare(r)
if err == nil {
if r.URL == nil {
return r, fmt.Errorf("ERROR: URL is not set")
}
r.URL.Path += path
}
return r, err
})
}
}
r, _ := Prepare(&http.Request{},
WithBaseURL("https://microsoft.com/"),
pd())
fmt.Printf("Path is %s\n", r.URL)
// Output: Path is https://microsoft.com/a/b/c/
}
// PrepareDecorators may also modify and then invoke the Preparer.
func ExamplePrepareDecorator_pre() {
pd := func() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
r.Header.Add(http.CanonicalHeaderKey("ContentType"), "application/json")
return p.Prepare(r)
})
}
}
r, _ := Prepare(&http.Request{Header: http.Header{}},
pd())
fmt.Printf("ContentType is %s\n", r.Header.Get("ContentType"))
// Output: ContentType is application/json
}
// Create a sequence of three Preparers that build up the URL path.
func ExampleCreatePreparer() {
p := CreatePreparer(
WithBaseURL("https://microsoft.com/"),
WithPath("a"),
WithPath("b"),
WithPath("c"))
r, err := p.Prepare(&http.Request{})
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a/b/c
}
// Create and apply separate Preparers
func ExampleCreatePreparer_multiple() {
params := map[string]interface{}{
"param1": "a",
"param2": "c",
}
p1 := CreatePreparer(WithBaseURL("https://microsoft.com/"))
p2 := CreatePreparer(WithPathParameters("/{param1}/b/{param2}/", params))
r, err := p1.Prepare(&http.Request{})
if err != nil {
fmt.Printf("ERROR: %v\n", err)
}
r, err = p2.Prepare(r)
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a/b/c/
}
// Create and chain separate Preparers
func ExampleCreatePreparer_chain() {
params := map[string]interface{}{
"param1": "a",
"param2": "c",
}
p := CreatePreparer(WithBaseURL("https://microsoft.com/"))
p = DecoratePreparer(p, WithPathParameters("/{param1}/b/{param2}/", params))
r, err := p.Prepare(&http.Request{})
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a/b/c/
}
// Create and prepare an http.Request in one call
func ExamplePrepare() {
r, err := Prepare(&http.Request{},
AsGet(),
WithBaseURL("https://microsoft.com/"),
WithPath("a/b/c/"))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Printf("%s %s", r.Method, r.URL)
}
// Output: GET https://microsoft.com/a/b/c/
}
// Create a request for a supplied base URL and path
func ExampleWithBaseURL() {
r, err := Prepare(&http.Request{},
WithBaseURL("https://microsoft.com/a/b/c/"))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a/b/c/
}
func ExampleWithBaseURL_second() {
_, err := Prepare(&http.Request{}, WithBaseURL(":"))
fmt.Println(err)
// Output: parse :: missing protocol scheme
}
func ExampleWithCustomBaseURL() {
r, err := Prepare(&http.Request{},
WithCustomBaseURL("https://{account}.{service}.core.windows.net/",
map[string]interface{}{
"account": "myaccount",
"service": "blob",
}))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://myaccount.blob.core.windows.net/
}
func ExampleWithCustomBaseURL_second() {
_, err := Prepare(&http.Request{},
WithCustomBaseURL(":", map[string]interface{}{}))
fmt.Println(err)
// Output: parse :: missing protocol scheme
}
// Create a request with a custom HTTP header
func ExampleWithHeader() {
r, err := Prepare(&http.Request{},
WithBaseURL("https://microsoft.com/a/b/c/"),
WithHeader("x-foo", "bar"))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Printf("Header %s=%s\n", "x-foo", r.Header.Get("x-foo"))
}
// Output: Header x-foo=bar
}
// Create a request whose Body is the JSON encoding of a structure
func ExampleWithFormData() {
v := url.Values{}
v.Add("name", "Rob Pike")
v.Add("age", "42")
r, err := Prepare(&http.Request{},
WithFormData(v))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Printf("Request Body contains %s\n", string(b))
}
// Output: Request Body contains age=42&name=Rob+Pike
}
// Create a request whose Body is the JSON encoding of a structure
func ExampleWithJSON() {
t := mocks.T{Name: "Rob Pike", Age: 42}
r, err := Prepare(&http.Request{},
WithJSON(&t))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Printf("Request Body contains %s\n", string(b))
}
// Output: Request Body contains {"name":"Rob Pike","age":42}
}
// Create a request from a path with escaped parameters
func ExampleWithEscapedPathParameters() {
params := map[string]interface{}{
"param1": "a b c",
"param2": "d e f",
}
r, err := Prepare(&http.Request{},
WithBaseURL("https://microsoft.com/"),
WithEscapedPathParameters("/{param1}/b/{param2}/", params))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a+b+c/b/d+e+f/
}
// Create a request from a path with parameters
func ExampleWithPathParameters() {
params := map[string]interface{}{
"param1": "a",
"param2": "c",
}
r, err := Prepare(&http.Request{},
WithBaseURL("https://microsoft.com/"),
WithPathParameters("/{param1}/b/{param2}/", params))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a/b/c/
}
// Create a request with query parameters
func ExampleWithQueryParameters() {
params := map[string]interface{}{
"q1": "value1",
"q2": "value2",
}
r, err := Prepare(&http.Request{},
WithBaseURL("https://microsoft.com/"),
WithPath("/a/b/c/"),
WithQueryParameters(params))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(r.URL)
}
// Output: https://microsoft.com/a/b/c/?q1=value1&q2=value2
}
func TestWithCustomBaseURL(t *testing.T) {
r, err := Prepare(&http.Request{}, WithCustomBaseURL("https://{account}.{service}.core.windows.net/",
map[string]interface{}{
"account": "myaccount",
"service": "blob",
}))
if err != nil {
t.Fatalf("autorest: WithCustomBaseURL should not fail")
}
if r.URL.String() != "https://myaccount.blob.core.windows.net/" {
t.Fatalf("autorest: WithCustomBaseURL expected https://myaccount.blob.core.windows.net/, got %s", r.URL)
}
}
func TestWithCustomBaseURLwithInvalidURL(t *testing.T) {
_, err := Prepare(&http.Request{}, WithCustomBaseURL("hello/{account}.{service}.core.windows.net/",
map[string]interface{}{
"account": "myaccount",
"service": "blob",
}))
if err == nil {
t.Fatalf("autorest: WithCustomBaseURL should fail fo URL parse error")
}
}
func TestWithPathWithInvalidPath(t *testing.T) {
p := "path%2*end"
if _, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPath(p)); err == nil {
t.Fatalf("autorest: WithPath should fail for invalid URL escape error for path '%v' ", p)
}
}
func TestWithPathParametersWithInvalidPath(t *testing.T) {
p := "path%2*end"
m := map[string]interface{}{
"path1": p,
}
if _, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPathParameters("/{path1}/", m)); err == nil {
t.Fatalf("autorest: WithPath should fail for invalid URL escape for path '%v' ", p)
}
}
func TestCreatePreparerDoesNotModify(t *testing.T) {
r1 := &http.Request{}
p := CreatePreparer()
r2, err := p.Prepare(r1)
if err != nil {
t.Fatalf("autorest: CreatePreparer failed (%v)", err)
}
if !reflect.DeepEqual(r1, r2) {
t.Fatalf("autorest: CreatePreparer without decorators modified the request")
}
}
func TestCreatePreparerRunsDecoratorsInOrder(t *testing.T) {
p := CreatePreparer(WithBaseURL("https://microsoft.com/"), WithPath("1"), WithPath("2"), WithPath("3"))
r, err := p.Prepare(&http.Request{})
if err != nil {
t.Fatalf("autorest: CreatePreparer failed (%v)", err)
}
if r.URL.String() != "https:/1/2/3" && r.URL.Host != "microsoft.com" {
t.Fatalf("autorest: CreatePreparer failed to run decorators in order")
}
}
func TestAsContentType(t *testing.T) {
r, err := Prepare(mocks.NewRequest(), AsContentType("application/text"))
if err != nil {
fmt.Printf("ERROR: %v", err)
}
if r.Header.Get(headerContentType) != "application/text" {
t.Fatalf("autorest: AsContentType failed to add header (%s=%s)", headerContentType, r.Header.Get(headerContentType))
}
}
func TestAsFormURLEncoded(t *testing.T) {
r, err := Prepare(mocks.NewRequest(), AsFormURLEncoded())
if err != nil {
fmt.Printf("ERROR: %v", err)
}
if r.Header.Get(headerContentType) != mimeTypeFormPost {
t.Fatalf("autorest: AsFormURLEncoded failed to add header (%s=%s)", headerContentType, r.Header.Get(headerContentType))
}
}
func TestAsJSON(t *testing.T) {
r, err := Prepare(mocks.NewRequest(), AsJSON())
if err != nil {
fmt.Printf("ERROR: %v", err)
}
if r.Header.Get(headerContentType) != mimeTypeJSON {
t.Fatalf("autorest: AsJSON failed to add header (%s=%s)", headerContentType, r.Header.Get(headerContentType))
}
}
func TestWithNothing(t *testing.T) {
r1 := mocks.NewRequest()
r2, err := Prepare(r1, WithNothing())
if err != nil {
t.Fatalf("autorest: WithNothing returned an unexpected error (%v)", err)
}
if !reflect.DeepEqual(r1, r2) {
t.Fatal("azure: WithNothing modified the passed HTTP Request")
}
}
func TestWithBearerAuthorization(t *testing.T) {
r, err := Prepare(mocks.NewRequest(), WithBearerAuthorization("SOME-TOKEN"))
if err != nil {
fmt.Printf("ERROR: %v", err)
}
if r.Header.Get(headerAuthorization) != "Bearer SOME-TOKEN" {
t.Fatalf("autorest: WithBearerAuthorization failed to add header (%s=%s)", headerAuthorization, r.Header.Get(headerAuthorization))
}
}
func TestWithUserAgent(t *testing.T) {
ua := "User Agent Go"
r, err := Prepare(mocks.NewRequest(), WithUserAgent(ua))
if err != nil {
fmt.Printf("ERROR: %v", err)
}
if r.UserAgent() != ua || r.Header.Get(headerUserAgent) != ua {
t.Fatalf("autorest: WithUserAgent failed to add header (%s=%s)", headerUserAgent, r.Header.Get(headerUserAgent))
}
}
func TestWithMethod(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), WithMethod("HEAD"))
if r.Method != "HEAD" {
t.Fatal("autorest: WithMethod failed to set HTTP method header")
}
}
func TestAsDelete(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsDelete())
if r.Method != "DELETE" {
t.Fatal("autorest: AsDelete failed to set HTTP method header to DELETE")
}
}
func TestAsGet(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsGet())
if r.Method != "GET" {
t.Fatal("autorest: AsGet failed to set HTTP method header to GET")
}
}
func TestAsHead(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsHead())
if r.Method != "HEAD" {
t.Fatal("autorest: AsHead failed to set HTTP method header to HEAD")
}
}
func TestAsOptions(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsOptions())
if r.Method != "OPTIONS" {
t.Fatal("autorest: AsOptions failed to set HTTP method header to OPTIONS")
}
}
func TestAsPatch(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsPatch())
if r.Method != "PATCH" {
t.Fatal("autorest: AsPatch failed to set HTTP method header to PATCH")
}
}
func TestAsPost(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsPost())
if r.Method != "POST" {
t.Fatal("autorest: AsPost failed to set HTTP method header to POST")
}
}
func TestAsPut(t *testing.T) {
r, _ := Prepare(mocks.NewRequest(), AsPut())
if r.Method != "PUT" {
t.Fatal("autorest: AsPut failed to set HTTP method header to PUT")
}
}
func TestPrepareWithNullRequest(t *testing.T) {
_, err := Prepare(nil)
if err == nil {
t.Fatal("autorest: Prepare failed to return an error when given a null http.Request")
}
}
func TestWithFormDataSetsContentLength(t *testing.T) {
v := url.Values{}
v.Add("name", "Rob Pike")
v.Add("age", "42")
r, err := Prepare(&http.Request{},
WithFormData(v))
if err != nil {
t.Fatalf("autorest: WithFormData failed with error (%v)", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithFormData failed with error (%v)", err)
}
expected := "name=Rob+Pike&age=42"
if !(string(b) == "name=Rob+Pike&age=42" || string(b) == "age=42&name=Rob+Pike") {
t.Fatalf("autorest:WithFormData failed to return correct string got (%v), expected (%v)", string(b), expected)
}
if r.ContentLength != int64(len(b)) {
t.Fatalf("autorest:WithFormData set Content-Length to %v, expected %v", r.ContentLength, len(b))
}
}
func TestWithMultiPartFormDataSetsContentLength(t *testing.T) {
v := map[string]interface{}{
"file": ioutil.NopCloser(strings.NewReader("Hello Gopher")),
"age": "42",
}
r, err := Prepare(&http.Request{},
WithMultiPartFormData(v))
if err != nil {
t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err)
}
if r.ContentLength != int64(len(b)) {
t.Fatalf("autorest:WithMultiPartFormData set Content-Length to %v, expected %v", r.ContentLength, len(b))
}
}
func TestWithMultiPartFormDataWithNoFile(t *testing.T) {
v := map[string]interface{}{
"file": "no file",
"age": "42",
}
r, err := Prepare(&http.Request{},
WithMultiPartFormData(v))
if err != nil {
t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err)
}
if r.ContentLength != int64(len(b)) {
t.Fatalf("autorest:WithMultiPartFormData set Content-Length to %v, expected %v", r.ContentLength, len(b))
}
}
func TestWithFile(t *testing.T) {
r, err := Prepare(&http.Request{},
WithFile(ioutil.NopCloser(strings.NewReader("Hello Gopher"))))
if err != nil {
t.Fatalf("autorest: WithFile failed with error (%v)", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithFile failed with error (%v)", err)
}
if r.ContentLength != int64(len(b)) {
t.Fatalf("autorest:WithFile set Content-Length to %v, expected %v", r.ContentLength, len(b))
}
}
func TestWithBool_SetsTheBody(t *testing.T) {
r, err := Prepare(&http.Request{},
WithBool(false))
if err != nil {
t.Fatalf("autorest: WithBool failed with error (%v)", err)
}
s, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithBool failed with error (%v)", err)
}
if r.ContentLength != int64(len(fmt.Sprintf("%v", false))) {
t.Fatalf("autorest: WithBool set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", false))))
}
v, err := strconv.ParseBool(string(s))
if err != nil || v {
t.Fatalf("autorest: WithBool incorrectly encoded the boolean as %v", s)
}
}
func TestWithFloat32_SetsTheBody(t *testing.T) {
r, err := Prepare(&http.Request{},
WithFloat32(42.0))
if err != nil {
t.Fatalf("autorest: WithFloat32 failed with error (%v)", err)
}
s, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithFloat32 failed with error (%v)", err)
}
if r.ContentLength != int64(len(fmt.Sprintf("%v", 42.0))) {
t.Fatalf("autorest: WithFloat32 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42.0))))
}
v, err := strconv.ParseFloat(string(s), 32)
if err != nil || float32(v) != float32(42.0) {
t.Fatalf("autorest: WithFloat32 incorrectly encoded the boolean as %v", s)
}
}
func TestWithFloat64_SetsTheBody(t *testing.T) {
r, err := Prepare(&http.Request{},
WithFloat64(42.0))
if err != nil {
t.Fatalf("autorest: WithFloat64 failed with error (%v)", err)
}
s, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithFloat64 failed with error (%v)", err)
}
if r.ContentLength != int64(len(fmt.Sprintf("%v", 42.0))) {
t.Fatalf("autorest: WithFloat64 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42.0))))
}
v, err := strconv.ParseFloat(string(s), 64)
if err != nil || v != float64(42.0) {
t.Fatalf("autorest: WithFloat64 incorrectly encoded the boolean as %v", s)
}
}
func TestWithInt32_SetsTheBody(t *testing.T) {
r, err := Prepare(&http.Request{},
WithInt32(42))
if err != nil {
t.Fatalf("autorest: WithInt32 failed with error (%v)", err)
}
s, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithInt32 failed with error (%v)", err)
}
if r.ContentLength != int64(len(fmt.Sprintf("%v", 42))) {
t.Fatalf("autorest: WithInt32 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42))))
}
v, err := strconv.ParseInt(string(s), 10, 32)
if err != nil || int32(v) != int32(42) {
t.Fatalf("autorest: WithInt32 incorrectly encoded the boolean as %v", s)
}
}
func TestWithInt64_SetsTheBody(t *testing.T) {
r, err := Prepare(&http.Request{},
WithInt64(42))
if err != nil {
t.Fatalf("autorest: WithInt64 failed with error (%v)", err)
}
s, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithInt64 failed with error (%v)", err)
}
if r.ContentLength != int64(len(fmt.Sprintf("%v", 42))) {
t.Fatalf("autorest: WithInt64 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42))))
}
v, err := strconv.ParseInt(string(s), 10, 64)
if err != nil || v != int64(42) {
t.Fatalf("autorest: WithInt64 incorrectly encoded the boolean as %v", s)
}
}
func TestWithString_SetsTheBody(t *testing.T) {
r, err := Prepare(&http.Request{},
WithString("value"))
if err != nil {
t.Fatalf("autorest: WithString failed with error (%v)", err)
}
s, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithString failed with error (%v)", err)
}
if r.ContentLength != int64(len("value")) {
t.Fatalf("autorest: WithString set Content-Length to %v, expected %v", r.ContentLength, int64(len("value")))
}
if string(s) != "value" {
t.Fatalf("autorest: WithString incorrectly encoded the string as %v", s)
}
}
func TestWithJSONSetsContentLength(t *testing.T) {
r, err := Prepare(&http.Request{},
WithJSON(&mocks.T{Name: "Rob Pike", Age: 42}))
if err != nil {
t.Fatalf("autorest: WithJSON failed with error (%v)", err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: WithJSON failed with error (%v)", err)
}
if r.ContentLength != int64(len(b)) {
t.Fatalf("autorest:WithJSON set Content-Length to %v, expected %v", r.ContentLength, len(b))
}
}
func TestWithHeaderAllocatesHeaders(t *testing.T) {
r, err := Prepare(mocks.NewRequest(), WithHeader("x-foo", "bar"))
if err != nil {
t.Fatalf("autorest: WithHeader failed (%v)", err)
}
if r.Header.Get("x-foo") != "bar" {
t.Fatalf("autorest: WithHeader failed to add header (%s=%s)", "x-foo", r.Header.Get("x-foo"))
}
}
func TestWithPathCatchesNilURL(t *testing.T) {
_, err := Prepare(&http.Request{}, WithPath("a"))
if err == nil {
t.Fatalf("autorest: WithPath failed to catch a nil URL")
}
}
func TestWithEscapedPathParametersCatchesNilURL(t *testing.T) {
_, err := Prepare(&http.Request{}, WithEscapedPathParameters("", map[string]interface{}{"foo": "bar"}))
if err == nil {
t.Fatalf("autorest: WithEscapedPathParameters failed to catch a nil URL")
}
}
func TestWithPathParametersCatchesNilURL(t *testing.T) {
_, err := Prepare(&http.Request{}, WithPathParameters("", map[string]interface{}{"foo": "bar"}))
if err == nil {
t.Fatalf("autorest: WithPathParameters failed to catch a nil URL")
}
}
func TestWithQueryParametersCatchesNilURL(t *testing.T) {
_, err := Prepare(&http.Request{}, WithQueryParameters(map[string]interface{}{"foo": "bar"}))
if err == nil {
t.Fatalf("autorest: WithQueryParameters failed to catch a nil URL")
}
}
func TestModifyingExistingRequest(t *testing.T) {
r, err := Prepare(mocks.NewRequestForURL("https://bing.com"), WithPath("search"), WithQueryParameters(map[string]interface{}{"q": "golang"}))
if err != nil {
t.Fatalf("autorest: Preparing an existing request returned an error (%v)", err)
}
if r.URL.String() != "https:/search?q=golang" && r.URL.Host != "bing.com" {
t.Fatalf("autorest: Preparing an existing request failed (%s)", r.URL)
}
}

View file

@ -1,665 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/Azure/go-autorest/autorest/mocks"
)
func ExampleWithErrorUnlessOK() {
r := mocks.NewResponse()
r.Request = mocks.NewRequest()
// Respond and leave the response body open (for a subsequent responder to close)
err := Respond(r,
WithErrorUnlessOK(),
ByDiscardingBody(),
ByClosingIfError())
if err == nil {
fmt.Printf("%s of %s returned HTTP 200", r.Request.Method, r.Request.URL)
// Complete handling the response and close the body
Respond(r,
ByDiscardingBody(),
ByClosing())
}
// Output: GET of https://microsoft.com/a/b/c/ returned HTTP 200
}
func ExampleByUnmarshallingJSON() {
c := `
{
"name" : "Rob Pike",
"age" : 42
}
`
type V struct {
Name string `json:"name"`
Age int `json:"age"`
}
v := &V{}
Respond(mocks.NewResponseWithContent(c),
ByUnmarshallingJSON(v),
ByClosing())
fmt.Printf("%s is %d years old\n", v.Name, v.Age)
// Output: Rob Pike is 42 years old
}
func ExampleByUnmarshallingXML() {
c := `<?xml version="1.0" encoding="UTF-8"?>
<Person>
<Name>Rob Pike</Name>
<Age>42</Age>
</Person>`
type V struct {
Name string `xml:"Name"`
Age int `xml:"Age"`
}
v := &V{}
Respond(mocks.NewResponseWithContent(c),
ByUnmarshallingXML(v),
ByClosing())
fmt.Printf("%s is %d years old\n", v.Name, v.Age)
// Output: Rob Pike is 42 years old
}
func TestCreateResponderDoesNotModify(t *testing.T) {
r1 := mocks.NewResponse()
r2 := mocks.NewResponse()
p := CreateResponder()
err := p.Respond(r1)
if err != nil {
t.Fatalf("autorest: CreateResponder failed (%v)", err)
}
if !reflect.DeepEqual(r1, r2) {
t.Fatalf("autorest: CreateResponder without decorators modified the response")
}
}
func TestCreateResponderRunsDecoratorsInOrder(t *testing.T) {
s := ""
d := func(n int) RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
err := r.Respond(resp)
if err == nil {
s += fmt.Sprintf("%d", n)
}
return err
})
}
}
p := CreateResponder(d(1), d(2), d(3))
err := p.Respond(&http.Response{})
if err != nil {
t.Fatalf("autorest: Respond failed (%v)", err)
}
if s != "123" {
t.Fatalf("autorest: CreateResponder invoked decorators in an incorrect order; expected '123', received '%s'", s)
}
}
func TestByIgnoring(t *testing.T) {
r := mocks.NewResponse()
Respond(r,
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(r2 *http.Response) error {
r1 := mocks.NewResponse()
if !reflect.DeepEqual(r1, r2) {
t.Fatalf("autorest: ByIgnoring modified the HTTP Response -- received %v, expected %v", r2, r1)
}
return nil
})
}
})(),
ByIgnoring(),
ByClosing())
}
func TestByCopying_Copies(t *testing.T) {
r := mocks.NewResponseWithContent(jsonT)
b := &bytes.Buffer{}
err := Respond(r,
ByCopying(b),
ByUnmarshallingJSON(&mocks.T{}),
ByClosing())
if err != nil {
t.Fatalf("autorest: ByCopying returned an unexpected error -- %v", err)
}
if b.String() != jsonT {
t.Fatalf("autorest: ByCopying failed to copy the bytes read")
}
}
func TestByCopying_ReturnsNestedErrors(t *testing.T) {
r := mocks.NewResponseWithContent(jsonT)
r.Body.Close()
err := Respond(r,
ByCopying(&bytes.Buffer{}),
ByUnmarshallingJSON(&mocks.T{}),
ByClosing())
if err == nil {
t.Fatalf("autorest: ByCopying failed to return the expected error")
}
}
func TestByCopying_AcceptsNilReponse(t *testing.T) {
r := mocks.NewResponse()
Respond(r,
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
r.Respond(nil)
return nil
})
}
})(),
ByCopying(&bytes.Buffer{}))
}
func TestByCopying_AcceptsNilBody(t *testing.T) {
r := mocks.NewResponse()
Respond(r,
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
resp.Body = nil
r.Respond(resp)
return nil
})
}
})(),
ByCopying(&bytes.Buffer{}))
}
func TestByClosing(t *testing.T) {
r := mocks.NewResponse()
err := Respond(r, ByClosing())
if err != nil {
t.Fatalf("autorest: ByClosing failed (%v)", err)
}
if r.Body.(*mocks.Body).IsOpen() {
t.Fatalf("autorest: ByClosing did not close the response body")
}
}
func TestByClosingAcceptsNilResponse(t *testing.T) {
r := mocks.NewResponse()
Respond(r,
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
r.Respond(nil)
return nil
})
}
})(),
ByClosing())
}
func TestByClosingAcceptsNilBody(t *testing.T) {
r := mocks.NewResponse()
Respond(r,
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
resp.Body = nil
r.Respond(resp)
return nil
})
}
})(),
ByClosing())
}
func TestByClosingClosesEvenAfterErrors(t *testing.T) {
var e error
r := mocks.NewResponse()
Respond(r,
withErrorRespondDecorator(&e),
ByClosing())
if r.Body.(*mocks.Body).IsOpen() {
t.Fatalf("autorest: ByClosing did not close the response body after an error occurred")
}
}
func TestByClosingClosesReturnsNestedErrors(t *testing.T) {
var e error
r := mocks.NewResponse()
err := Respond(r,
withErrorRespondDecorator(&e),
ByClosing())
if err == nil || !reflect.DeepEqual(e, err) {
t.Fatalf("autorest: ByClosing failed to return a nested error")
}
}
func TestByClosingIfErrorAcceptsNilResponse(t *testing.T) {
var e error
r := mocks.NewResponse()
Respond(r,
withErrorRespondDecorator(&e),
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
r.Respond(nil)
return nil
})
}
})(),
ByClosingIfError())
}
func TestByClosingIfErrorAcceptsNilBody(t *testing.T) {
var e error
r := mocks.NewResponse()
Respond(r,
withErrorRespondDecorator(&e),
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
resp.Body = nil
r.Respond(resp)
return nil
})
}
})(),
ByClosingIfError())
}
func TestByClosingIfErrorClosesIfAnErrorOccurs(t *testing.T) {
var e error
r := mocks.NewResponse()
Respond(r,
withErrorRespondDecorator(&e),
ByClosingIfError())
if r.Body.(*mocks.Body).IsOpen() {
t.Fatalf("autorest: ByClosingIfError did not close the response body after an error occurred")
}
}
func TestByClosingIfErrorDoesNotClosesIfNoErrorOccurs(t *testing.T) {
r := mocks.NewResponse()
Respond(r,
ByClosingIfError())
if !r.Body.(*mocks.Body).IsOpen() {
t.Fatalf("autorest: ByClosingIfError closed the response body even though no error occurred")
}
}
func TestByDiscardingBody(t *testing.T) {
r := mocks.NewResponse()
err := Respond(r,
ByDiscardingBody())
if err != nil {
t.Fatalf("autorest: ByDiscardingBody failed (%v)", err)
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: Reading result of ByDiscardingBody failed (%v)", err)
}
if len(buf) != 0 {
t.Logf("autorest: Body was not empty after calling ByDiscardingBody.")
t.Fail()
}
}
func TestByDiscardingBodyAcceptsNilResponse(t *testing.T) {
var e error
r := mocks.NewResponse()
Respond(r,
withErrorRespondDecorator(&e),
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
r.Respond(nil)
return nil
})
}
})(),
ByDiscardingBody())
}
func TestByDiscardingBodyAcceptsNilBody(t *testing.T) {
var e error
r := mocks.NewResponse()
Respond(r,
withErrorRespondDecorator(&e),
(func() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
resp.Body.Close()
resp.Body = nil
r.Respond(resp)
return nil
})
}
})(),
ByDiscardingBody())
}
func TestByUnmarshallingJSON(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err != nil {
t.Fatalf("autorest: ByUnmarshallingJSON failed (%v)", err)
}
if v.Name != "Rob Pike" || v.Age != 42 {
t.Fatalf("autorest: ByUnmarshallingJSON failed to properly unmarshal")
}
}
func TestByUnmarshallingJSON_HandlesReadErrors(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
r.Body.(*mocks.Body).Close()
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err == nil {
t.Fatalf("autorest: ByUnmarshallingJSON failed to receive / respond to read error")
}
}
func TestByUnmarshallingJSONIncludesJSONInErrors(t *testing.T) {
v := &mocks.T{}
j := jsonT[0 : len(jsonT)-2]
r := mocks.NewResponseWithContent(j)
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err == nil || !strings.Contains(err.Error(), j) {
t.Fatalf("autorest: ByUnmarshallingJSON failed to return JSON in error (%v)", err)
}
}
func TestByUnmarshallingJSONEmptyInput(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(``)
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err != nil {
t.Fatalf("autorest: ByUnmarshallingJSON failed to return nil in case of empty JSON (%v)", err)
}
}
func TestByUnmarshallingXML(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(xmlT)
err := Respond(r,
ByUnmarshallingXML(v),
ByClosing())
if err != nil {
t.Fatalf("autorest: ByUnmarshallingXML failed (%v)", err)
}
if v.Name != "Rob Pike" || v.Age != 42 {
t.Fatalf("autorest: ByUnmarshallingXML failed to properly unmarshal")
}
}
func TestByUnmarshallingXML_HandlesReadErrors(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(xmlT)
r.Body.(*mocks.Body).Close()
err := Respond(r,
ByUnmarshallingXML(v),
ByClosing())
if err == nil {
t.Fatalf("autorest: ByUnmarshallingXML failed to receive / respond to read error")
}
}
func TestByUnmarshallingXMLIncludesXMLInErrors(t *testing.T) {
v := &mocks.T{}
x := xmlT[0 : len(xmlT)-2]
r := mocks.NewResponseWithContent(x)
err := Respond(r,
ByUnmarshallingXML(v),
ByClosing())
if err == nil || !strings.Contains(err.Error(), x) {
t.Fatalf("autorest: ByUnmarshallingXML failed to return XML in error (%v)", err)
}
}
func TestRespondAcceptsNullResponse(t *testing.T) {
err := Respond(nil)
if err != nil {
t.Fatalf("autorest: Respond returned an unexpected error when given a null Response (%v)", err)
}
}
func TestWithErrorUnlessStatusCodeOKResponse(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
err := Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
ByUnmarshallingJSON(v),
ByClosing())
if err != nil {
t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) failed on okay response. (%v)", err)
}
if v.Name != "Rob Pike" || v.Age != 42 {
t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) corrupted the response body of okay response.")
}
}
func TesWithErrorUnlessStatusCodeErrorResponse(t *testing.T) {
v := &mocks.T{}
e := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
r.Status = "400 BadRequest"
r.StatusCode = http.StatusBadRequest
err := Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
ByUnmarshallingJSON(v),
ByClosing())
if err == nil {
t.Fatal("autorest: WithErrorUnlessStatusCode(http.StatusOK) did not return error, on a response to a bad request.")
}
var errorRespBody []byte
if derr, ok := err.(DetailedError); !ok {
t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) got wrong error type : %T, expected: DetailedError, on a response to a bad request.", err)
} else {
errorRespBody = derr.ServiceError
}
if errorRespBody == nil {
t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) ServiceError not returned in DetailedError on a response to a bad request.")
}
err = json.Unmarshal(errorRespBody, e)
if err != nil {
t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) cannot parse error returned in ServiceError into json. %v", err)
}
expected := &mocks.T{Name: "Rob Pike", Age: 42}
if e != expected {
t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK wrong value from parsed ServiceError: got=%#v expected=%#v", e, expected)
}
}
func TestWithErrorUnlessStatusCode(t *testing.T) {
r := mocks.NewResponse()
r.Request = mocks.NewRequest()
r.Status = "400 BadRequest"
r.StatusCode = http.StatusBadRequest
err := Respond(r,
WithErrorUnlessStatusCode(http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError),
ByClosingIfError())
if err != nil {
t.Fatalf("autorest: WithErrorUnlessStatusCode returned an error (%v) for an acceptable status code (%s)", err, r.Status)
}
}
func TestWithErrorUnlessStatusCodeEmitsErrorForUnacceptableStatusCode(t *testing.T) {
r := mocks.NewResponse()
r.Request = mocks.NewRequest()
r.Status = "400 BadRequest"
r.StatusCode = http.StatusBadRequest
err := Respond(r,
WithErrorUnlessStatusCode(http.StatusOK, http.StatusUnauthorized, http.StatusInternalServerError),
ByClosingIfError())
if err == nil {
t.Fatalf("autorest: WithErrorUnlessStatusCode failed to return an error for an unacceptable status code (%s)", r.Status)
}
}
func TestWithErrorUnlessOK(t *testing.T) {
r := mocks.NewResponse()
r.Request = mocks.NewRequest()
err := Respond(r,
WithErrorUnlessOK(),
ByClosingIfError())
if err != nil {
t.Fatalf("autorest: WithErrorUnlessOK returned an error for OK status code (%v)", err)
}
}
func TestWithErrorUnlessOKEmitsErrorIfNotOK(t *testing.T) {
r := mocks.NewResponse()
r.Request = mocks.NewRequest()
r.Status = "400 BadRequest"
r.StatusCode = http.StatusBadRequest
err := Respond(r,
WithErrorUnlessOK(),
ByClosingIfError())
if err == nil {
t.Fatalf("autorest: WithErrorUnlessOK failed to return an error for a non-OK status code (%v)", err)
}
}
func TestExtractHeader(t *testing.T) {
r := mocks.NewResponse()
v := []string{"v1", "v2", "v3"}
mocks.SetResponseHeaderValues(r, mocks.TestHeader, v)
if !reflect.DeepEqual(ExtractHeader(mocks.TestHeader, r), v) {
t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v",
mocks.TestHeader, v, mocks.TestHeader, ExtractHeader(mocks.TestHeader, r))
}
}
func TestExtractHeaderHandlesMissingHeader(t *testing.T) {
var v []string
r := mocks.NewResponse()
if !reflect.DeepEqual(ExtractHeader(mocks.TestHeader, r), v) {
t.Fatalf("autorest: ExtractHeader failed to handle a missing header -- expected %v, received %v",
v, ExtractHeader(mocks.TestHeader, r))
}
}
func TestExtractHeaderValue(t *testing.T) {
r := mocks.NewResponse()
v := "v1"
mocks.SetResponseHeader(r, mocks.TestHeader, v)
if ExtractHeaderValue(mocks.TestHeader, r) != v {
t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v",
mocks.TestHeader, v, mocks.TestHeader, ExtractHeaderValue(mocks.TestHeader, r))
}
}
func TestExtractHeaderValueHandlesMissingHeader(t *testing.T) {
r := mocks.NewResponse()
v := ""
if ExtractHeaderValue(mocks.TestHeader, r) != v {
t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v",
mocks.TestHeader, v, mocks.TestHeader, ExtractHeaderValue(mocks.TestHeader, r))
}
}
func TestExtractHeaderValueRetrievesFirstValue(t *testing.T) {
r := mocks.NewResponse()
v := []string{"v1", "v2", "v3"}
mocks.SetResponseHeaderValues(r, mocks.TestHeader, v)
if ExtractHeaderValue(mocks.TestHeader, r) != v[0] {
t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v",
mocks.TestHeader, v[0], mocks.TestHeader, ExtractHeaderValue(mocks.TestHeader, r))
}
}

View file

@ -215,20 +215,26 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
rr := NewRetriableRequest(r)
// Increment to add the first call (attempts denotes number of retries)
attempts++
for attempt := 0; attempt < attempts; attempt++ {
for attempt := 0; attempt < attempts; {
err = rr.Prepare()
if err != nil {
return resp, err
}
resp, err = s.Do(rr.Request())
// we want to retry if err is not nil (e.g. transient network failure)
if err == nil && !ResponseHasStatusCode(resp, codes...) {
// we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication
// resp and err will both have a value, so in this case we don't want to retry as it will never succeed.
if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) {
return resp, err
}
delayed := DelayWithRetryAfter(resp, r.Cancel)
if !delayed {
DelayForBackoff(backoff, attempt, r.Cancel)
}
// don't count a 429 against the number of attempts
// so that we continue to retry until it succeeds
if resp == nil || resp.StatusCode != http.StatusTooManyRequests {
attempt++
}
}
return resp, err
})

View file

@ -1,811 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"bytes"
"fmt"
"log"
"net/http"
"os"
"reflect"
"sync"
"testing"
"time"
"github.com/Azure/go-autorest/autorest/mocks"
)
func ExampleSendWithSender() {
r := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(r)
client := mocks.NewSender()
client.AppendAndRepeatResponse(r, 10)
logger := log.New(os.Stdout, "autorest: ", 0)
na := NullAuthorizer{}
req, _ := Prepare(&http.Request{},
AsGet(),
WithBaseURL("https://microsoft.com/a/b/c/"),
na.WithAuthorization())
r, _ = SendWithSender(client, req,
WithLogging(logger),
DoErrorIfStatusCode(http.StatusAccepted),
DoCloseIfError(),
DoRetryForAttempts(5, time.Duration(0)))
Respond(r,
ByDiscardingBody(),
ByClosing())
// Output:
// autorest: Sending GET https://microsoft.com/a/b/c/
// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
// autorest: Sending GET https://microsoft.com/a/b/c/
// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
// autorest: Sending GET https://microsoft.com/a/b/c/
// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
// autorest: Sending GET https://microsoft.com/a/b/c/
// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
// autorest: Sending GET https://microsoft.com/a/b/c/
// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
}
func ExampleDoRetryForAttempts() {
client := mocks.NewSender()
client.SetAndRepeatError(fmt.Errorf("Faux Error"), 10)
// Retry with backoff -- ensure returned Bodies are closed
r, _ := SendWithSender(client, mocks.NewRequest(),
DoCloseIfError(),
DoRetryForAttempts(5, time.Duration(0)))
Respond(r,
ByDiscardingBody(),
ByClosing())
fmt.Printf("Retry stopped after %d attempts", client.Attempts())
// Output: Retry stopped after 5 attempts
}
func ExampleDoErrorIfStatusCode() {
client := mocks.NewSender()
client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 NoContent", http.StatusNoContent), 10)
// Chain decorators to retry the request, up to five times, if the status code is 204
r, _ := SendWithSender(client, mocks.NewRequest(),
DoErrorIfStatusCode(http.StatusNoContent),
DoCloseIfError(),
DoRetryForAttempts(5, time.Duration(0)))
Respond(r,
ByDiscardingBody(),
ByClosing())
fmt.Printf("Retry stopped after %d attempts with code %s", client.Attempts(), r.Status)
// Output: Retry stopped after 5 attempts with code 204 NoContent
}
func TestSendWithSenderRunsDecoratorsInOrder(t *testing.T) {
client := mocks.NewSender()
s := ""
r, err := SendWithSender(client, mocks.NewRequest(),
withMessage(&s, "a"),
withMessage(&s, "b"),
withMessage(&s, "c"))
if err != nil {
t.Fatalf("autorest: SendWithSender returned an error (%v)", err)
}
Respond(r,
ByDiscardingBody(),
ByClosing())
if s != "abc" {
t.Fatalf("autorest: SendWithSender invoke decorators out of order; expected 'abc', received '%s'", s)
}
}
func TestCreateSender(t *testing.T) {
f := false
s := CreateSender(
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
f = true
return nil, nil
})
}
})())
s.Do(&http.Request{})
if !f {
t.Fatal("autorest: CreateSender failed to apply supplied decorator")
}
}
func TestSend(t *testing.T) {
f := false
Send(&http.Request{},
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
f = true
return nil, nil
})
}
})())
if !f {
t.Fatal("autorest: Send failed to apply supplied decorator")
}
}
func TestAfterDelayWaits(t *testing.T) {
client := mocks.NewSender()
d := 2 * time.Second
tt := time.Now()
r, _ := SendWithSender(client, mocks.NewRequest(),
AfterDelay(d))
s := time.Since(tt)
if s < d {
t.Fatal("autorest: AfterDelay failed to wait for at least the specified duration")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestAfterDelay_Cancels(t *testing.T) {
client := mocks.NewSender()
cancel := make(chan struct{})
delay := 5 * time.Second
var wg sync.WaitGroup
wg.Add(1)
tt := time.Now()
go func() {
req := mocks.NewRequest()
req.Cancel = cancel
wg.Done()
SendWithSender(client, req,
AfterDelay(delay))
}()
wg.Wait()
close(cancel)
time.Sleep(5 * time.Millisecond)
if time.Since(tt) >= delay {
t.Fatal("autorest: AfterDelay failed to cancel")
}
}
func TestAfterDelayDoesNotWaitTooLong(t *testing.T) {
client := mocks.NewSender()
d := 5 * time.Millisecond
start := time.Now()
r, _ := SendWithSender(client, mocks.NewRequest(),
AfterDelay(d))
if time.Since(start) > (5 * d) {
t.Fatal("autorest: AfterDelay waited too long (exceeded 5 times specified duration)")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestAsIs(t *testing.T) {
client := mocks.NewSender()
r1 := mocks.NewResponse()
client.AppendResponse(r1)
r2, err := SendWithSender(client, mocks.NewRequest(),
AsIs())
if err != nil {
t.Fatalf("autorest: AsIs returned an unexpected error (%v)", err)
} else if !reflect.DeepEqual(r1, r2) {
t.Fatalf("autorest: AsIs modified the response -- received %v, expected %v", r2, r1)
}
Respond(r1,
ByDiscardingBody(),
ByClosing())
Respond(r2,
ByDiscardingBody(),
ByClosing())
}
func TestDoCloseIfError(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest))
r, _ := SendWithSender(client, mocks.NewRequest(),
DoErrorIfStatusCode(http.StatusBadRequest),
DoCloseIfError())
if r.Body.(*mocks.Body).IsOpen() {
t.Fatal("autorest: Expected DoCloseIfError to close response body -- it was left open")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoCloseIfErrorAcceptsNilResponse(t *testing.T) {
client := mocks.NewSender()
SendWithSender(client, mocks.NewRequest(),
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
resp, err := s.Do(r)
if err != nil {
resp.Body.Close()
}
return nil, fmt.Errorf("Faux Error")
})
}
})(),
DoCloseIfError())
}
func TestDoCloseIfErrorAcceptsNilBody(t *testing.T) {
client := mocks.NewSender()
SendWithSender(client, mocks.NewRequest(),
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
resp, err := s.Do(r)
if err != nil {
resp.Body.Close()
}
resp.Body = nil
return resp, fmt.Errorf("Faux Error")
})
}
})(),
DoCloseIfError())
}
func TestDoErrorIfStatusCode(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest))
r, err := SendWithSender(client, mocks.NewRequest(),
DoErrorIfStatusCode(http.StatusBadRequest),
DoCloseIfError())
if err == nil {
t.Fatal("autorest: DoErrorIfStatusCode failed to emit an error for passed code")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoErrorIfStatusCodeIgnoresStatusCodes(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(newAcceptedResponse())
r, err := SendWithSender(client, mocks.NewRequest(),
DoErrorIfStatusCode(http.StatusBadRequest),
DoCloseIfError())
if err != nil {
t.Fatal("autorest: DoErrorIfStatusCode failed to ignore a status code")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoErrorUnlessStatusCode(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest))
r, err := SendWithSender(client, mocks.NewRequest(),
DoErrorUnlessStatusCode(http.StatusAccepted),
DoCloseIfError())
if err == nil {
t.Fatal("autorest: DoErrorUnlessStatusCode failed to emit an error for an unknown status code")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoErrorUnlessStatusCodeIgnoresStatusCodes(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(newAcceptedResponse())
r, err := SendWithSender(client, mocks.NewRequest(),
DoErrorUnlessStatusCode(http.StatusAccepted),
DoCloseIfError())
if err != nil {
t.Fatal("autorest: DoErrorUnlessStatusCode emitted an error for a knonwn status code")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForAttemptsStopsAfterSuccess(t *testing.T) {
client := mocks.NewSender()
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForAttempts(5, time.Duration(0)))
if client.Attempts() != 1 {
t.Fatalf("autorest: DoRetryForAttempts failed to stop after success -- expected attempts %v, actual %v",
1, client.Attempts())
}
if err != nil {
t.Fatalf("autorest: DoRetryForAttempts returned an unexpected error (%v)", err)
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForAttemptsStopsAfterAttempts(t *testing.T) {
client := mocks.NewSender()
client.SetAndRepeatError(fmt.Errorf("Faux Error"), 10)
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForAttempts(5, time.Duration(0)),
DoCloseIfError())
if err == nil {
t.Fatal("autorest: Mock client failed to emit errors")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
if client.Attempts() != 5 {
t.Fatal("autorest: DoRetryForAttempts failed to stop after specified number of attempts")
}
}
func TestDoRetryForAttemptsReturnsResponse(t *testing.T) {
client := mocks.NewSender()
client.SetError(fmt.Errorf("Faux Error"))
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForAttempts(1, time.Duration(0)))
if err == nil {
t.Fatal("autorest: Mock client failed to emit errors")
}
if r == nil {
t.Fatal("autorest: DoRetryForAttempts failed to return the underlying response")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForDurationStopsAfterSuccess(t *testing.T) {
client := mocks.NewSender()
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForDuration(10*time.Millisecond, time.Duration(0)))
if client.Attempts() != 1 {
t.Fatalf("autorest: DoRetryForDuration failed to stop after success -- expected attempts %v, actual %v",
1, client.Attempts())
}
if err != nil {
t.Fatalf("autorest: DoRetryForDuration returned an unexpected error (%v)", err)
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForDurationStopsAfterDuration(t *testing.T) {
client := mocks.NewSender()
client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1)
d := 5 * time.Millisecond
start := time.Now()
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForDuration(d, time.Duration(0)),
DoCloseIfError())
if err == nil {
t.Fatal("autorest: Mock client failed to emit errors")
}
if time.Since(start) < d {
t.Fatal("autorest: DoRetryForDuration failed stopped too soon")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForDurationStopsWithinReason(t *testing.T) {
client := mocks.NewSender()
client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1)
d := 5 * time.Second
start := time.Now()
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForDuration(d, time.Duration(0)),
DoCloseIfError())
if err == nil {
t.Fatal("autorest: Mock client failed to emit errors")
}
if time.Since(start) > (5 * d) {
t.Fatal("autorest: DoRetryForDuration failed stopped soon enough (exceeded 5 times specified duration)")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForDurationReturnsResponse(t *testing.T) {
client := mocks.NewSender()
client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1)
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForDuration(10*time.Millisecond, time.Duration(0)),
DoCloseIfError())
if err == nil {
t.Fatal("autorest: Mock client failed to emit errors")
}
if r == nil {
t.Fatal("autorest: DoRetryForDuration failed to return the underlying response")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDelayForBackoff(t *testing.T) {
d := 2 * time.Second
start := time.Now()
DelayForBackoff(d, 0, nil)
if time.Since(start) < d {
t.Fatal("autorest: DelayForBackoff did not delay as long as expected")
}
}
func TestDelayForBackoff_Cancels(t *testing.T) {
cancel := make(chan struct{})
delay := 5 * time.Second
var wg sync.WaitGroup
wg.Add(1)
start := time.Now()
go func() {
wg.Done()
DelayForBackoff(delay, 0, cancel)
}()
wg.Wait()
close(cancel)
time.Sleep(5 * time.Millisecond)
if time.Since(start) >= delay {
t.Fatal("autorest: DelayForBackoff failed to cancel")
}
}
func TestDelayForBackoffWithinReason(t *testing.T) {
d := 5 * time.Second
maxCoefficient := 2
start := time.Now()
DelayForBackoff(d, 0, nil)
if time.Since(start) > (time.Duration(maxCoefficient) * d) {
t.Fatalf("autorest: DelayForBackoff delayed too long (exceeded %d times the specified duration)", maxCoefficient)
}
}
func TestDoPollForStatusCodes_IgnoresUnspecifiedStatusCodes(t *testing.T) {
client := mocks.NewSender()
r, _ := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Duration(0), time.Duration(0)))
if client.Attempts() != 1 {
t.Fatalf("autorest: Sender#DoPollForStatusCodes polled for unspecified status code")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoPollForStatusCodes_PollsForSpecifiedStatusCodes(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(newAcceptedResponse())
r, _ := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
if client.Attempts() != 2 {
t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to poll for specified status code")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoPollForStatusCodes_CanBeCanceled(t *testing.T) {
cancel := make(chan struct{})
delay := 5 * time.Second
r := mocks.NewResponse()
mocks.SetAcceptedHeaders(r)
client := mocks.NewSender()
client.AppendAndRepeatResponse(r, 100)
var wg sync.WaitGroup
wg.Add(1)
start := time.Now()
go func() {
wg.Done()
r, _ := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
Respond(r,
ByDiscardingBody(),
ByClosing())
}()
wg.Wait()
close(cancel)
time.Sleep(5 * time.Millisecond)
if time.Since(start) >= delay {
t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to cancel")
}
}
func TestDoPollForStatusCodes_ClosesAllNonreturnedResponseBodiesWhenPolling(t *testing.T) {
resp := newAcceptedResponse()
client := mocks.NewSender()
client.AppendAndRepeatResponse(resp, 2)
r, _ := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
if resp.Body.(*mocks.Body).IsOpen() || resp.Body.(*mocks.Body).CloseAttempts() < 2 {
t.Fatalf("autorest: Sender#DoPollForStatusCodes did not close unreturned response bodies")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoPollForStatusCodes_LeavesLastResponseBodyOpen(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(newAcceptedResponse())
r, _ := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
if !r.Body.(*mocks.Body).IsOpen() {
t.Fatalf("autorest: Sender#DoPollForStatusCodes did not leave open the body of the last response")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoPollForStatusCodes_StopsPollingAfterAnError(t *testing.T) {
client := mocks.NewSender()
client.AppendAndRepeatResponse(newAcceptedResponse(), 5)
client.SetError(fmt.Errorf("Faux Error"))
client.SetEmitErrorAfter(1)
r, _ := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
if client.Attempts() > 2 {
t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to stop polling after receiving an error")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoPollForStatusCodes_ReturnsPollingError(t *testing.T) {
client := mocks.NewSender()
client.AppendAndRepeatResponse(newAcceptedResponse(), 5)
client.SetError(fmt.Errorf("Faux Error"))
client.SetEmitErrorAfter(1)
r, err := SendWithSender(client, mocks.NewRequest(),
DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
if err == nil {
t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to return error from polling")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestWithLogging_Logs(t *testing.T) {
buf := &bytes.Buffer{}
logger := log.New(buf, "autorest: ", 0)
client := mocks.NewSender()
r, _ := SendWithSender(client, &http.Request{},
WithLogging(logger))
if buf.String() == "" {
t.Fatal("autorest: Sender#WithLogging failed to log the request")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestWithLogging_HandlesMissingResponse(t *testing.T) {
buf := &bytes.Buffer{}
logger := log.New(buf, "autorest: ", 0)
client := mocks.NewSender()
client.AppendResponse(nil)
client.SetError(fmt.Errorf("Faux Error"))
r, err := SendWithSender(client, &http.Request{},
WithLogging(logger))
if r != nil || err == nil {
t.Fatal("autorest: Sender#WithLogging returned a valid response -- expecting nil")
}
if buf.String() == "" {
t.Fatal("autorest: Sender#WithLogging failed to log the request for a nil response")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
}
func TestDoRetryForStatusCodesWithSuccess(t *testing.T) {
client := mocks.NewSender()
client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("408 Request Timeout", http.StatusRequestTimeout), 2)
client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
r, _ := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(5, time.Duration(2*time.Second), http.StatusRequestTimeout),
)
Respond(r,
ByDiscardingBody(),
ByClosing())
if client.Attempts() != 3 {
t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ",
r.Status, client.Attempts()-1)
}
}
func TestDoRetryForStatusCodesWithNoSuccess(t *testing.T) {
client := mocks.NewSender()
client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("504 Gateway Timeout", http.StatusGatewayTimeout), 5)
r, _ := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(2, time.Duration(2*time.Second), http.StatusGatewayTimeout),
)
Respond(r,
ByDiscardingBody(),
ByClosing())
if client.Attempts() != 3 {
t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: failed stop after %v retry attempts; Want: Stop after 2 retry attempts",
client.Attempts()-1)
}
}
func TestDoRetryForStatusCodes_CodeNotInRetryList(t *testing.T) {
client := mocks.NewSender()
client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 No Content", http.StatusNoContent), 1)
r, _ := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(6, time.Duration(2*time.Second), http.StatusGatewayTimeout),
)
Respond(r,
ByDiscardingBody(),
ByClosing())
if client.Attempts() != 1 || r.Status != "204 No Content" {
t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: Retry attempts %v for StatusCode %v; Want: 0 attempts for StatusCode 204",
client.Attempts(), r.Status)
}
}
func TestDoRetryForStatusCodes_RequestBodyReadError(t *testing.T) {
client := mocks.NewSender()
client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 No Content", http.StatusNoContent), 2)
r, err := SendWithSender(client, mocks.NewRequestWithCloseBody(),
DoRetryForStatusCodes(6, time.Duration(2*time.Second), http.StatusGatewayTimeout),
)
Respond(r,
ByDiscardingBody(),
ByClosing())
if err == nil || client.Attempts() != 0 {
t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: Not failed for request body read error; Want: Failed for body read error - %v", err)
}
}
func newAcceptedResponse() *http.Response {
resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
mocks.SetAcceptedHeaders(resp)
return resp
}
func TestDelayWithRetryAfterWithSuccess(t *testing.T) {
after, retries := 5, 2
totalSecs := after * retries
client := mocks.NewSender()
resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests)
mocks.SetResponseHeader(resp, "Retry-After", fmt.Sprintf("%v", after))
client.AppendAndRepeatResponse(resp, retries)
client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
d := time.Second * time.Duration(totalSecs)
start := time.Now()
r, _ := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(5, time.Duration(time.Second), http.StatusTooManyRequests),
)
if time.Since(start) < d {
t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
if client.Attempts() != 3 {
t.Fatalf("autorest: Sender#DelayWithRetryAfter -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ",
r.Status, client.Attempts()-1)
}
}

View file

@ -23,8 +23,9 @@ import (
"net/http"
"net/url"
"reflect"
"sort"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
)
// EncodedAs is a series of constants specifying various data encodings
@ -138,13 +139,38 @@ func MapToValues(m map[string]interface{}) url.Values {
return v
}
// String method converts interface v to string. If interface is a list, it
// joins list elements using separator.
func String(v interface{}, sep ...string) string {
if len(sep) > 0 {
return ensureValueString(strings.Join(v.([]string), sep[0]))
// AsStringSlice method converts interface{} to []string. This expects a
//that the parameter passed to be a slice or array of a type that has the underlying
//type a string.
func AsStringSlice(s interface{}) ([]string, error) {
v := reflect.ValueOf(s)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, NewError("autorest", "AsStringSlice", "the value's type is not an array.")
}
return ensureValueString(v)
stringSlice := make([]string, 0, v.Len())
for i := 0; i < v.Len(); i++ {
stringSlice = append(stringSlice, v.Index(i).String())
}
return stringSlice, nil
}
// String method converts interface v to string. If interface is a list, it
// joins list elements using the seperator. Note that only sep[0] will be used for
// joining if any separator is specified.
func String(v interface{}, sep ...string) string {
if len(sep) == 0 {
return ensureValueString(v)
}
stringSlice, ok := v.([]string)
if ok == false {
var err error
stringSlice, err = AsStringSlice(v)
if err != nil {
panic(fmt.Sprintf("autorest: Couldn't convert value to a string %s.", err))
}
}
return ensureValueString(strings.Join(stringSlice, sep[0]))
}
// Encode method encodes url path and query parameters.
@ -168,30 +194,6 @@ func queryEscape(s string) string {
return url.QueryEscape(s)
}
// This method is same as Encode() method of "net/url" go package,
// except it does not encode the query parameters because they
// already come encoded. It formats values map in query format (bar=foo&a=b).
func createQuery(v url.Values) string {
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
prefix := url.QueryEscape(k) + "="
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(prefix)
buf.WriteString(v)
}
}
return buf.String()
}
// ChangeToGet turns the specified http.Request into a GET (it assumes it wasn't).
// This is mainly useful for long-running operations that use the Azure-AsyncOperation
// header, so we change the initial PUT into a GET to retrieve the final result.
@ -202,3 +204,15 @@ func ChangeToGet(req *http.Request) *http.Request {
req.Header.Del("Content-Length")
return req
}
// IsTokenRefreshError returns true if the specified error implements the TokenRefreshError
// interface. If err is a DetailedError it will walk the chain of Original errors.
func IsTokenRefreshError(err error) bool {
if _, ok := err.(adal.TokenRefreshError); ok {
return true
}
if de, ok := err.(DetailedError); ok {
return IsTokenRefreshError(de.Original)
}
return false
}

View file

@ -1,382 +0,0 @@
package autorest
// Copyright 2017 Microsoft Corporation
//
// 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.
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"net/http"
"net/url"
"reflect"
"sort"
"strings"
"testing"
"github.com/Azure/go-autorest/autorest/mocks"
)
const (
jsonT = `
{
"name":"Rob Pike",
"age":42
}`
xmlT = `<?xml version="1.0" encoding="UTF-8"?>
<Person>
<Name>Rob Pike</Name>
<Age>42</Age>
</Person>`
)
func TestNewDecoderCreatesJSONDecoder(t *testing.T) {
d := NewDecoder(EncodedAsJSON, strings.NewReader(jsonT))
_, ok := d.(*json.Decoder)
if d == nil || !ok {
t.Fatal("autorest: NewDecoder failed to create a JSON decoder when requested")
}
}
func TestNewDecoderCreatesXMLDecoder(t *testing.T) {
d := NewDecoder(EncodedAsXML, strings.NewReader(xmlT))
_, ok := d.(*xml.Decoder)
if d == nil || !ok {
t.Fatal("autorest: NewDecoder failed to create an XML decoder when requested")
}
}
func TestNewDecoderReturnsNilForUnknownEncoding(t *testing.T) {
d := NewDecoder("unknown", strings.NewReader(xmlT))
if d != nil {
t.Fatal("autorest: NewDecoder created a decoder for an unknown encoding")
}
}
func TestCopyAndDecodeDecodesJSON(t *testing.T) {
_, err := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT), &mocks.T{})
if err != nil {
t.Fatalf("autorest: CopyAndDecode returned an error with valid JSON - %v", err)
}
}
func TestCopyAndDecodeDecodesXML(t *testing.T) {
_, err := CopyAndDecode(EncodedAsXML, strings.NewReader(xmlT), &mocks.T{})
if err != nil {
t.Fatalf("autorest: CopyAndDecode returned an error with valid XML - %v", err)
}
}
func TestCopyAndDecodeReturnsJSONDecodingErrors(t *testing.T) {
_, err := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT[0:len(jsonT)-2]), &mocks.T{})
if err == nil {
t.Fatalf("autorest: CopyAndDecode failed to return an error with invalid JSON")
}
}
func TestCopyAndDecodeReturnsXMLDecodingErrors(t *testing.T) {
_, err := CopyAndDecode(EncodedAsXML, strings.NewReader(xmlT[0:len(xmlT)-2]), &mocks.T{})
if err == nil {
t.Fatalf("autorest: CopyAndDecode failed to return an error with invalid XML")
}
}
func TestCopyAndDecodeAlwaysReturnsACopy(t *testing.T) {
b, _ := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT), &mocks.T{})
if b.String() != jsonT {
t.Fatalf("autorest: CopyAndDecode failed to return a valid copy of the data - %v", b.String())
}
}
func TestTeeReadCloser_Copies(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
b := &bytes.Buffer{}
r.Body = TeeReadCloser(r.Body, b)
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err != nil {
t.Fatalf("autorest: TeeReadCloser returned an unexpected error -- %v", err)
}
if b.String() != jsonT {
t.Fatalf("autorest: TeeReadCloser failed to copy the bytes read")
}
}
func TestTeeReadCloser_PassesReadErrors(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
r.Body.(*mocks.Body).Close()
r.Body = TeeReadCloser(r.Body, &bytes.Buffer{})
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err == nil {
t.Fatalf("autorest: TeeReadCloser failed to return the expected error")
}
}
func TestTeeReadCloser_ClosesWrappedReader(t *testing.T) {
v := &mocks.T{}
r := mocks.NewResponseWithContent(jsonT)
b := r.Body.(*mocks.Body)
r.Body = TeeReadCloser(r.Body, &bytes.Buffer{})
err := Respond(r,
ByUnmarshallingJSON(v),
ByClosing())
if err != nil {
t.Fatalf("autorest: TeeReadCloser returned an unexpected error -- %v", err)
}
if b.IsOpen() {
t.Fatalf("autorest: TeeReadCloser failed to close the nested io.ReadCloser")
}
}
func TestContainsIntFindsValue(t *testing.T) {
ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
v := 5
if !containsInt(ints, v) {
t.Fatalf("autorest: containsInt failed to find %v in %v", v, ints)
}
}
func TestContainsIntDoesNotFindValue(t *testing.T) {
ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
v := 42
if containsInt(ints, v) {
t.Fatalf("autorest: containsInt unexpectedly found %v in %v", v, ints)
}
}
func TestContainsIntAcceptsEmptyList(t *testing.T) {
ints := make([]int, 10)
if containsInt(ints, 42) {
t.Fatalf("autorest: containsInt failed to handle an empty list")
}
}
func TestContainsIntAcceptsNilList(t *testing.T) {
var ints []int
if containsInt(ints, 42) {
t.Fatalf("autorest: containsInt failed to handle an nil list")
}
}
func TestEscapeStrings(t *testing.T) {
m := map[string]string{
"string": "a long string with = odd characters",
"int": "42",
"nil": "",
}
r := map[string]string{
"string": "a+long+string+with+%3D+odd+characters",
"int": "42",
"nil": "",
}
v := escapeValueStrings(m)
if !reflect.DeepEqual(v, r) {
t.Fatalf("autorest: ensureValueStrings returned %v\n", v)
}
}
func TestEnsureStrings(t *testing.T) {
m := map[string]interface{}{
"string": "string",
"int": 42,
"nil": nil,
"bytes": []byte{255, 254, 253},
}
r := map[string]string{
"string": "string",
"int": "42",
"nil": "",
"bytes": string([]byte{255, 254, 253}),
}
v := ensureValueStrings(m)
if !reflect.DeepEqual(v, r) {
t.Fatalf("autorest: ensureValueStrings returned %v\n", v)
}
}
func ExampleString() {
m := []string{
"string1",
"string2",
"string3",
}
fmt.Println(String(m, ","))
// Output: string1,string2,string3
}
func TestStringWithValidString(t *testing.T) {
i := 123
if String(i) != "123" {
t.Fatal("autorest: String method failed to convert integer 123 to string")
}
}
func TestEncodeWithValidPath(t *testing.T) {
s := Encode("Path", "Hello Gopher")
if s != "Hello%20Gopher" {
t.Fatalf("autorest: Encode method failed for valid path encoding. Got: %v; Want: %v", s, "Hello%20Gopher")
}
}
func TestEncodeWithValidQuery(t *testing.T) {
s := Encode("Query", "Hello Gopher")
if s != "Hello+Gopher" {
t.Fatalf("autorest: Encode method failed for valid query encoding. Got: '%v'; Want: 'Hello+Gopher'", s)
}
}
func TestEncodeWithValidNotPathQuery(t *testing.T) {
s := Encode("Host", "Hello Gopher")
if s != "Hello Gopher" {
t.Fatalf("autorest: Encode method failed for parameter not query or path. Got: '%v'; Want: 'Hello Gopher'", s)
}
}
func TestMapToValues(t *testing.T) {
m := map[string]interface{}{
"a": "a",
"b": 2,
}
v := url.Values{}
v.Add("a", "a")
v.Add("b", "2")
if !isEqual(v, MapToValues(m)) {
t.Fatalf("autorest: MapToValues method failed to return correct values - expected(%v) got(%v)", v, MapToValues(m))
}
}
func TestMapToValuesWithArrayValues(t *testing.T) {
m := map[string]interface{}{
"a": []string{"a", "b"},
"b": 2,
"c": []int{3, 4},
}
v := url.Values{}
v.Add("a", "a")
v.Add("a", "b")
v.Add("b", "2")
v.Add("c", "3")
v.Add("c", "4")
if !isEqual(v, MapToValues(m)) {
t.Fatalf("autorest: MapToValues method failed to return correct values - expected(%v) got(%v)", v, MapToValues(m))
}
}
func isEqual(v, u url.Values) bool {
for key, value := range v {
if len(u[key]) == 0 {
return false
}
sort.Strings(value)
sort.Strings(u[key])
for i := range value {
if value[i] != u[key][i] {
return false
}
}
u.Del(key)
}
if len(u) > 0 {
return false
}
return true
}
func doEnsureBodyClosed(t *testing.T) SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
resp, err := s.Do(r)
if resp != nil && resp.Body != nil && resp.Body.(*mocks.Body).IsOpen() {
t.Fatal("autorest: Expected Body to be closed -- it was left open")
}
return resp, err
})
}
}
type mockAuthorizer struct{}
func (ma mockAuthorizer) WithAuthorization() PrepareDecorator {
return WithHeader(headerAuthorization, mocks.TestAuthorizationHeader)
}
type mockFailingAuthorizer struct{}
func (mfa mockFailingAuthorizer) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
return r, fmt.Errorf("ERROR: mockFailingAuthorizer returned expected error")
})
}
}
type mockInspector struct {
wasInvoked bool
}
func (mi *mockInspector) WithInspection() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
mi.wasInvoked = true
return p.Prepare(r)
})
}
}
func (mi *mockInspector) ByInspecting() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
mi.wasInvoked = true
return r.Respond(resp)
})
}
}
func withMessage(output *string, msg string) SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
resp, err := s.Do(r)
if err == nil {
*output += msg
}
return resp, err
})
}
}
func withErrorRespondDecorator(e *error) RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
err := r.Respond(resp)
if err != nil {
return err
}
*e = fmt.Errorf("autorest: Faux Respond Error")
return *e
})
}
}

View file

@ -22,9 +22,9 @@ import (
)
const (
major = 8
minor = 0
patch = 0
major = 9
minor = 8
patch = 1
tag = ""
)

View file

@ -28,12 +28,13 @@ var (
// passed in as an argument. If the function returns an error due to the source
// being disallowed, it should return ErrInvalidUpstream.
//
// Behavior is as follows:
// * If error is not nil, the call to Accept() will fail. If the reason for
// If error is not nil, the call to Accept() will fail. If the reason for
// triggering this failure is due to a disallowed source, it should return
// ErrInvalidUpstream.
// * If bool is true, the PROXY-set address is used.
// * If bool is false, the connection's remote address is used, rather than the
//
// If bool is true, the PROXY-set address is used.
//
// If bool is false, the connection's remote address is used, rather than the
// address claimed in the PROXY info.
type SourceChecker func(net.Addr) (bool, error)

View file

@ -1,383 +0,0 @@
package proxyproto
import (
"bytes"
"net"
"testing"
"time"
)
const (
goodAddr = "127.0.0.1"
badAddr = "127.0.0.2"
errAddr = "9999.0.0.2"
)
var (
checkAddr string
)
func TestPassthrough(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
pl := &Listener{Listener: l}
go func() {
conn, err := net.Dial("tcp", pl.Addr().String())
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
conn.Write([]byte("ping"))
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("pong")) {
t.Fatalf("bad: %v", recv)
}
}()
conn, err := pl.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("ping")) {
t.Fatalf("bad: %v", recv)
}
if _, err := conn.Write([]byte("pong")); err != nil {
t.Fatalf("err: %v", err)
}
}
func TestTimeout(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
clientWriteDelay := 200 * time.Millisecond
proxyHeaderTimeout := 50 * time.Millisecond
pl := &Listener{Listener: l, ProxyHeaderTimeout: proxyHeaderTimeout}
go func() {
conn, err := net.Dial("tcp", pl.Addr().String())
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Do not send data for a while
time.Sleep(clientWriteDelay)
conn.Write([]byte("ping"))
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("pong")) {
t.Fatalf("bad: %v", recv)
}
}()
conn, err := pl.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Check the remote addr is the original 127.0.0.1
remoteAddrStartTime := time.Now()
addr := conn.RemoteAddr().(*net.TCPAddr)
if addr.IP.String() != "127.0.0.1" {
t.Fatalf("bad: %v", addr)
}
remoteAddrDuration := time.Since(remoteAddrStartTime)
// Check RemoteAddr() call did timeout
if remoteAddrDuration >= clientWriteDelay {
t.Fatalf("RemoteAddr() took longer than the specified timeout: %v < %v", proxyHeaderTimeout, remoteAddrDuration)
}
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("ping")) {
t.Fatalf("bad: %v", recv)
}
if _, err := conn.Write([]byte("pong")); err != nil {
t.Fatalf("err: %v", err)
}
}
func TestParse_ipv4(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
pl := &Listener{Listener: l}
go func() {
conn, err := net.Dial("tcp", pl.Addr().String())
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Write out the header!
header := "PROXY TCP4 10.1.1.1 20.2.2.2 1000 2000\r\n"
conn.Write([]byte(header))
conn.Write([]byte("ping"))
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("pong")) {
t.Fatalf("bad: %v", recv)
}
}()
conn, err := pl.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("ping")) {
t.Fatalf("bad: %v", recv)
}
if _, err := conn.Write([]byte("pong")); err != nil {
t.Fatalf("err: %v", err)
}
// Check the remote addr
addr := conn.RemoteAddr().(*net.TCPAddr)
if addr.IP.String() != "10.1.1.1" {
t.Fatalf("bad: %v", addr)
}
if addr.Port != 1000 {
t.Fatalf("bad: %v", addr)
}
}
func TestParse_ipv6(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
pl := &Listener{Listener: l}
go func() {
conn, err := net.Dial("tcp", pl.Addr().String())
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Write out the header!
header := "PROXY TCP6 ffff::ffff ffff::ffff 1000 2000\r\n"
conn.Write([]byte(header))
conn.Write([]byte("ping"))
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("pong")) {
t.Fatalf("bad: %v", recv)
}
}()
conn, err := pl.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("ping")) {
t.Fatalf("bad: %v", recv)
}
if _, err := conn.Write([]byte("pong")); err != nil {
t.Fatalf("err: %v", err)
}
// Check the remote addr
addr := conn.RemoteAddr().(*net.TCPAddr)
if addr.IP.String() != "ffff::ffff" {
t.Fatalf("bad: %v", addr)
}
if addr.Port != 1000 {
t.Fatalf("bad: %v", addr)
}
}
func TestParse_BadHeader(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
pl := &Listener{Listener: l}
go func() {
conn, err := net.Dial("tcp", pl.Addr().String())
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Write out the header!
header := "PROXY TCP4 what 127.0.0.1 1000 2000\r\n"
conn.Write([]byte(header))
conn.Write([]byte("ping"))
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err == nil {
t.Fatalf("err: %v", err)
}
}()
conn, err := pl.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Check the remote addr, should be the local addr
addr := conn.RemoteAddr().(*net.TCPAddr)
if addr.IP.String() != "127.0.0.1" {
t.Fatalf("bad: %v", addr)
}
// Read should fail
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err == nil {
t.Fatalf("err: %v", err)
}
}
func TestParse_ipv4_checkfunc(t *testing.T) {
checkAddr = goodAddr
testParse_ipv4_checkfunc(t)
checkAddr = badAddr
testParse_ipv4_checkfunc(t)
checkAddr = errAddr
testParse_ipv4_checkfunc(t)
}
func testParse_ipv4_checkfunc(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
checkFunc := func(addr net.Addr) (bool, error) {
tcpAddr := addr.(*net.TCPAddr)
if tcpAddr.IP.String() == checkAddr {
return true, nil
}
return false, nil
}
pl := &Listener{Listener: l, SourceCheck: checkFunc}
go func() {
conn, err := net.Dial("tcp", pl.Addr().String())
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
// Write out the header!
header := "PROXY TCP4 10.1.1.1 20.2.2.2 1000 2000\r\n"
conn.Write([]byte(header))
conn.Write([]byte("ping"))
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("pong")) {
t.Fatalf("bad: %v", recv)
}
}()
conn, err := pl.Accept()
if err != nil {
if checkAddr == badAddr {
return
}
t.Fatalf("err: %v", err)
}
defer conn.Close()
recv := make([]byte, 4)
_, err = conn.Read(recv)
if err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(recv, []byte("ping")) {
t.Fatalf("bad: %v", recv)
}
if _, err := conn.Write([]byte("pong")); err != nil {
t.Fatalf("err: %v", err)
}
// Check the remote addr
addr := conn.RemoteAddr().(*net.TCPAddr)
switch checkAddr {
case goodAddr:
if addr.IP.String() != "10.1.1.1" {
t.Fatalf("bad: %v", addr)
}
if addr.Port != 1000 {
t.Fatalf("bad: %v", addr)
}
case badAddr:
if addr.IP.String() != "127.0.0.1" {
t.Fatalf("bad: %v", addr)
}
if addr.Port == 1000 {
t.Fatalf("bad: %v", addr)
}
}
}

View file

@ -1,63 +0,0 @@
package quantile
import (
"testing"
)
func BenchmarkInsertTargeted(b *testing.B) {
b.ReportAllocs()
s := NewTargeted(Targets)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiased(b *testing.B) {
s := NewLowBiased(0.01)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) {
s := NewLowBiased(0.0001)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkQuery(b *testing.B) {
s := NewTargeted(Targets)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}
func BenchmarkQuerySmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}

View file

@ -1,121 +0,0 @@
// +build go1.1
package quantile_test
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/beorn7/perks/quantile"
)
func Example_simple() {
ch := make(chan float64)
go sendFloats(ch)
// Compute the 50th, 90th, and 99th percentile.
q := quantile.NewTargeted(map[float64]float64{
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
})
for v := range ch {
q.Insert(v)
}
fmt.Println("perc50:", q.Query(0.50))
fmt.Println("perc90:", q.Query(0.90))
fmt.Println("perc99:", q.Query(0.99))
fmt.Println("count:", q.Count())
// Output:
// perc50: 5
// perc90: 16
// perc99: 223
// count: 2388
}
func Example_mergeMultipleStreams() {
// Scenario:
// We have multiple database shards. On each shard, there is a process
// collecting query response times from the database logs and inserting
// them into a Stream (created via NewTargeted(0.90)), much like the
// Simple example. These processes expose a network interface for us to
// ask them to serialize and send us the results of their
// Stream.Samples so we may Merge and Query them.
//
// NOTES:
// * These sample sets are small, allowing us to get them
// across the network much faster than sending the entire list of data
// points.
//
// * For this to work correctly, we must supply the same quantiles
// a priori the process collecting the samples supplied to NewTargeted,
// even if we do not plan to query them all here.
ch := make(chan quantile.Samples)
getDBQuerySamples(ch)
q := quantile.NewTargeted(map[float64]float64{0.90: 0.001})
for samples := range ch {
q.Merge(samples)
}
fmt.Println("perc90:", q.Query(0.90))
}
func Example_window() {
// Scenario: We want the 90th, 95th, and 99th percentiles for each
// minute.
ch := make(chan float64)
go sendStreamValues(ch)
tick := time.NewTicker(1 * time.Minute)
q := quantile.NewTargeted(map[float64]float64{
0.90: 0.001,
0.95: 0.0005,
0.99: 0.0001,
})
for {
select {
case t := <-tick.C:
flushToDB(t, q.Samples())
q.Reset()
case v := <-ch:
q.Insert(v)
}
}
}
func sendStreamValues(ch chan float64) {
// Use your imagination
}
func flushToDB(t time.Time, samples quantile.Samples) {
// Use your imagination
}
// This is a stub for the above example. In reality this would hit the remote
// servers via http or something like it.
func getDBQuerySamples(ch chan quantile.Samples) {}
func sendFloats(ch chan<- float64) {
f, err := os.Open("exampledata.txt")
if err != nil {
log.Fatal(err)
}
sc := bufio.NewScanner(f)
for sc.Scan() {
b := sc.Bytes()
v, err := strconv.ParseFloat(string(b), 64)
if err != nil {
log.Fatal(err)
}
ch <- v
}
if sc.Err() != nil {
log.Fatal(sc.Err())
}
close(ch)
}

View file

@ -77,15 +77,20 @@ func NewHighBiased(epsilon float64) *Stream {
// is guaranteed to be within (Quantile±Epsilon).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targets map[float64]float64) *Stream {
func NewTargeted(targetMap map[float64]float64) *Stream {
// Convert map to slice to avoid slow iterations on a map.
// ƒ is called on the hot path, so converting the map to a slice
// beforehand results in significant CPU savings.
targets := targetMapToSlice(targetMap)
ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64
var f float64
for quantile, epsilon := range targets {
if quantile*s.n <= r {
f = (2 * epsilon * r) / quantile
for _, t := range targets {
if t.quantile*s.n <= r {
f = (2 * t.epsilon * r) / t.quantile
} else {
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
}
if f < m {
m = f
@ -96,6 +101,25 @@ func NewTargeted(targets map[float64]float64) *Stream {
return newStream(ƒ)
}
type target struct {
quantile float64
epsilon float64
}
func targetMapToSlice(targetMap map[float64]float64) []target {
targets := make([]target, 0, len(targetMap))
for quantile, epsilon := range targetMap {
t := target{
quantile: quantile,
epsilon: epsilon,
}
targets = append(targets, t)
}
return targets
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines.
type Stream struct {

View file

@ -1,215 +0,0 @@
package quantile
import (
"math"
"math/rand"
"sort"
"testing"
)
var (
Targets = map[float64]float64{
0.01: 0.001,
0.10: 0.01,
0.50: 0.05,
0.90: 0.01,
0.99: 0.001,
}
TargetsSmallEpsilon = map[float64]float64{
0.01: 0.0001,
0.10: 0.001,
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
}
LowQuantiles = []float64{0.01, 0.1, 0.5}
HighQuantiles = []float64{0.99, 0.9, 0.5}
)
const RelativeEpsilon = 0.01
func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for quantile, epsilon := range Targets {
n := float64(len(a))
k := int(quantile * n)
if k < 1 {
k = 1
}
lower := int((quantile - epsilon) * n)
if lower < 1 {
lower = 1
}
upper := int(math.Ceil((quantile + epsilon) * n))
if upper > len(a) {
upper = len(a)
}
w, min, max := a[k-1], a[lower-1], a[upper-1]
if g := s.Query(quantile); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g)
}
}
}
func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range LowQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - RelativeEpsilon) * qu * n)
upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range HighQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n)
upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func populateStream(s *Stream) []float64 {
a := make([]float64, 0, 1e5+100)
for i := 0; i < cap(a); i++ {
v := rand.NormFloat64()
// Add 5% asymmetric outliers.
if i%20 == 0 {
v = v*v + 1
}
s.Insert(v)
a = append(a, v)
}
return a
}
func TestTargetedQuery(t *testing.T) {
rand.Seed(42)
s := NewTargeted(Targets)
a := populateStream(s)
verifyPercsWithAbsoluteEpsilon(t, a, s)
}
func TestTargetedQuerySmallSampleSize(t *testing.T) {
rand.Seed(42)
s := NewTargeted(TargetsSmallEpsilon)
a := []float64{1, 2, 3, 4, 5}
for _, v := range a {
s.Insert(v)
}
verifyPercsWithAbsoluteEpsilon(t, a, s)
// If not yet flushed, results should be precise:
if !s.flushed() {
for φ, want := range map[float64]float64{
0.01: 1,
0.10: 1,
0.50: 3,
0.90: 5,
0.99: 5,
} {
if got := s.Query(φ); got != want {
t.Errorf("want %f for φ=%f, got %f", want, φ, got)
}
}
}
}
func TestLowBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewLowBiased(RelativeEpsilon)
a := populateStream(s)
verifyLowPercsWithRelativeEpsilon(t, a, s)
}
func TestHighBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewHighBiased(RelativeEpsilon)
a := populateStream(s)
verifyHighPercsWithRelativeEpsilon(t, a, s)
}
// BrokenTestTargetedMerge is broken, see Merge doc comment.
func BrokenTestTargetedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewTargeted(Targets)
s2 := NewTargeted(Targets)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyPercsWithAbsoluteEpsilon(t, a, s1)
}
// BrokenTestLowBiasedMerge is broken, see Merge doc comment.
func BrokenTestLowBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewLowBiased(RelativeEpsilon)
s2 := NewLowBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyLowPercsWithRelativeEpsilon(t, a, s2)
}
// BrokenTestHighBiasedMerge is broken, see Merge doc comment.
func BrokenTestHighBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewHighBiased(RelativeEpsilon)
s2 := NewHighBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyHighPercsWithRelativeEpsilon(t, a, s2)
}
func TestUncompressed(t *testing.T) {
q := NewTargeted(Targets)
for i := 100; i > 0; i-- {
q.Insert(float64(i))
}
if g := q.Count(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
// Before compression, Query should have 100% accuracy.
for quantile := range Targets {
w := quantile * 100
if g := q.Query(quantile); g != w {
t.Errorf("want %f, got %f", w, g)
}
}
}
func TestUncompressedSamples(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.001})
for i := 1; i <= 100; i++ {
q.Insert(float64(i))
}
if g := q.Samples().Len(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
}
func TestUncompressedOne(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.01})
q.Insert(3.14)
if g := q.Query(0.90); g != 3.14 {
t.Error("want PI, got", g)
}
}
func TestDefaults(t *testing.T) {
if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 {
t.Errorf("want 0, got %f", g)
}
}

View file

@ -1,298 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
// custom type to test Stinger interface on non-pointer receiver.
type stringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with non-pointer receivers.
func (s stringer) String() string {
return "stringer " + string(s)
}
// custom type to test Stinger interface on pointer receiver.
type pstringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with only pointer receivers.
func (s *pstringer) String() string {
return "stringer " + string(*s)
}
// xref1 and xref2 are cross referencing structs for testing circular reference
// detection.
type xref1 struct {
ps2 *xref2
}
type xref2 struct {
ps1 *xref1
}
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
// reference for testing detection.
type indirCir1 struct {
ps2 *indirCir2
}
type indirCir2 struct {
ps3 *indirCir3
}
type indirCir3 struct {
ps1 *indirCir1
}
// embed is used to test embedded structures.
type embed struct {
a string
}
// embedwrap is used to test embedded structures.
type embedwrap struct {
*embed
e *embed
}
// panicer is used to intentionally cause a panic for testing spew properly
// handles them
type panicer int
func (p panicer) String() string {
panic("test panic")
}
// customError is used to test custom error interface invocation.
type customError int
func (e customError) Error() string {
return fmt.Sprintf("error: %d", int(e))
}
// stringizeWants converts a slice of wanted test output into a format suitable
// for a test error message.
func stringizeWants(wants []string) string {
s := ""
for i, want := range wants {
if i > 0 {
s += fmt.Sprintf("want%d: %s", i+1, want)
} else {
s += "want: " + want
}
}
return s
}
// testFailed returns whether or not a test failed by checking if the result
// of the test is in the slice of wanted strings.
func testFailed(result string, wants []string) bool {
for _, want := range wants {
if result == want {
return false
}
}
return true
}
type sortableStruct struct {
x int
}
func (ss sortableStruct) String() string {
return fmt.Sprintf("ss.%d", ss.x)
}
type unsortableStruct struct {
x int
}
type sortTestCase struct {
input []reflect.Value
expected []reflect.Value
}
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
getInterfaces := func(values []reflect.Value) []interface{} {
interfaces := []interface{}{}
for _, v := range values {
interfaces = append(interfaces, v.Interface())
}
return interfaces
}
for _, test := range tests {
spew.SortValues(test.input, cs)
// reflect.DeepEqual cannot really make sense of reflect.Value,
// probably because of all the pointer tricks. For instance,
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
// instead.
input := getInterfaces(test.input)
expected := getInterfaces(test.expected)
if !reflect.DeepEqual(input, expected) {
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
}
}
}
// TestSortValues ensures the sort functionality for relect.Value based sorting
// works as intended.
func TestSortValues(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
embedA := v(embed{"a"})
embedB := v(embed{"b"})
embedC := v(embed{"c"})
tests := []sortTestCase{
// No values.
{
[]reflect.Value{},
[]reflect.Value{},
},
// Bools.
{
[]reflect.Value{v(false), v(true), v(false)},
[]reflect.Value{v(false), v(false), v(true)},
},
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Uints.
{
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
},
// Floats.
{
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// Array
{
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
},
// Uintptrs.
{
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
},
// SortableStructs.
{
// Note: not sorted - DisableMethods is set.
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
// Invalid.
{
[]reflect.Value{embedB, embedA, embedC},
[]reflect.Value{embedB, embedA, embedC},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
// based sorting works as intended when using string methods.
func TestSortValuesWithMethods(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
// based sorting works as intended when using spew to stringify keys.
func TestSortValuesWithSpew(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
helpTestSortValues(tests, &cs, t)
}

File diff suppressed because it is too large Load diff

View file

@ -1,99 +0,0 @@
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This means the cgo tests are only added (and hence run) when
// specifially requested. This configuration is used because spew itself
// does not require cgo to run even though it does handle certain cgo types
// specially. Rather than forcing all clients to require cgo and an external
// C compiler just to run the tests, this scheme makes them optional.
// +build cgo,testcgo
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew/testdata"
)
func addCgoDumpTests() {
// C char pointer.
v := testdata.GetCgoCharPointer()
nv := testdata.GetCgoNullCharPointer()
pv := &v
vcAddr := fmt.Sprintf("%p", v)
vAddr := fmt.Sprintf("%p", pv)
pvAddr := fmt.Sprintf("%p", &pv)
vt := "*testdata._Ctype_char"
vs := "116"
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(nv, "("+vt+")(<nil>)\n")
// C char array.
v2, v2l, v2c := testdata.GetCgoCharArray()
v2Len := fmt.Sprintf("%d", v2l)
v2Cap := fmt.Sprintf("%d", v2c)
v2t := "[6]testdata._Ctype_char"
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
"{\n 00000000 74 65 73 74 32 00 " +
" |test2.|\n}"
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
// C unsigned char array.
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
v3Len := fmt.Sprintf("%d", v3l)
v3Cap := fmt.Sprintf("%d", v3c)
v3t := "[6]testdata._Ctype_unsignedchar"
v3t2 := "[6]testdata._Ctype_uchar"
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
"{\n 00000000 74 65 73 74 33 00 " +
" |test3.|\n}"
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
// C signed char array.
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
v4Len := fmt.Sprintf("%d", v4l)
v4Cap := fmt.Sprintf("%d", v4c)
v4t := "[6]testdata._Ctype_schar"
v4t2 := "testdata._Ctype_schar"
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
") 0\n}"
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
// C uint8_t array.
v5, v5l, v5c := testdata.GetCgoUint8tArray()
v5Len := fmt.Sprintf("%d", v5l)
v5Cap := fmt.Sprintf("%d", v5c)
v5t := "[6]testdata._Ctype_uint8_t"
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
"{\n 00000000 74 65 73 74 35 00 " +
" |test5.|\n}"
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
// C typedefed unsigned char array.
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
v6Len := fmt.Sprintf("%d", v6l)
v6Cap := fmt.Sprintf("%d", v6c)
v6t := "[6]testdata._Ctype_custom_uchar_t"
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
"{\n 00000000 74 65 73 74 36 00 " +
" |test6.|\n}"
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
}

View file

@ -1,26 +0,0 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either cgo is not supported or "-tags testcgo" is not added to the go
// test command line. This file intentionally does not setup any cgo tests in
// this scenario.
// +build !cgo !testcgo
package spew_test
func addCgoDumpTests() {
// Don't add any tests for cgo since this file is only compiled when
// there should not be any cgo tests.
}

View file

@ -1,226 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew"
)
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
// This example demonstrates how to use Dump to dump variables to stdout.
func ExampleDump() {
// The following package level declarations are assumed for this example:
/*
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
*/
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
f := Flag(5)
b := []byte{
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32,
}
// Dump!
spew.Dump(s1, f, b)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Flag) Unknown flag (5)
// ([]uint8) (len=34 cap=34) {
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
// 00000020 31 32 |12|
// }
//
}
// This example demonstrates how to use Printf to display a variable with a
// format string and inline formatting.
func ExamplePrintf() {
// Create a double pointer to a uint 8.
ui8 := uint8(5)
pui8 := &ui8
ppui8 := &pui8
// Create a circular data type.
type circular struct {
ui8 uint8
c *circular
}
c := circular{ui8: 1}
c.c = &c
// Print!
spew.Printf("ppui8: %v\n", ppui8)
spew.Printf("circular: %v\n", c)
// Output:
// ppui8: <**>5
// circular: {1 <*>{1 <*><shown>}}
}
// This example demonstrates how to use a ConfigState.
func ExampleConfigState() {
// Modify the indent level of the ConfigState only. The global
// configuration is not modified.
scs := spew.ConfigState{Indent: "\t"}
// Output using the ConfigState instance.
v := map[string]int{"one": 1}
scs.Printf("v: %v\n", v)
scs.Dump(v)
// Output:
// v: map[one:1]
// (map[string]int) (len=1) {
// (string) (len=3) "one": (int) 1
// }
}
// This example demonstrates how to use ConfigState.Dump to dump variables to
// stdout
func ExampleConfigState_Dump() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances with different indentation.
scs := spew.ConfigState{Indent: "\t"}
scs2 := spew.ConfigState{Indent: " "}
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
// Dump using the ConfigState instances.
scs.Dump(s1)
scs2.Dump(s1)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
//
}
// This example demonstrates how to use ConfigState.Printf to display a variable
// with a format string and inline formatting.
func ExampleConfigState_Printf() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances and modify the method handling of the
// first ConfigState only.
scs := spew.NewDefaultConfig()
scs2 := spew.NewDefaultConfig()
scs.DisableMethods = true
// Alternatively
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
// scs2 := spew.ConfigState{Indent: " "}
// This is of type Flag which implements a Stringer and has raw value 1.
f := flagTwo
// Dump using the ConfigState instances.
scs.Printf("f: %v\n", f)
scs2.Printf("f: %v\n", f)
// Output:
// f: 1
// f: flagTwo
}

File diff suppressed because it is too large Load diff

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
)
// dummyFmtState implements a fake fmt.State to use for testing invalid
// reflect.Value handling. This is necessary because the fmt package catches
// invalid values before invoking the formatter on them.
type dummyFmtState struct {
bytes.Buffer
}
func (dfs *dummyFmtState) Flag(f int) bool {
if f == int('+') {
return true
}
return false
}
func (dfs *dummyFmtState) Precision() (int, bool) {
return 0, false
}
func (dfs *dummyFmtState) Width() (int, bool) {
return 0, false
}
// TestInvalidReflectValue ensures the dump and formatter code handles an
// invalid reflect value properly. This needs access to internal state since it
// should never happen in real code and therefore can't be tested via the public
// API.
func TestInvalidReflectValue(t *testing.T) {
i := 1
// Dump invalid reflect value.
v := new(reflect.Value)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(*v)
s := buf.String()
want := "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter invalid reflect value.
buf2 := new(dummyFmtState)
f := formatState{value: *v, cs: &Config, fs: buf2}
f.format(*v)
s = buf2.String()
want = "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
}
}
// SortValues makes the internal sortValues function available to the test
// package.
func SortValues(values []reflect.Value, cs *ConfigState) {
sortValues(values, cs)
}

View file

@ -1,102 +0,0 @@
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
"unsafe"
)
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
// the maximum kind value which does not exist. This is needed to test the
// fallback code which punts to the standard fmt library for new types that
// might get added to the language.
func changeKind(v *reflect.Value, readOnly bool) {
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
if readOnly {
*rvf |= flagRO
} else {
*rvf &= ^uintptr(flagRO)
}
}
// TestAddedReflectValue tests functionaly of the dump and formatter code which
// falls back to the standard fmt library for new types that might get added to
// the language.
func TestAddedReflectValue(t *testing.T) {
i := 1
// Dump using a reflect.Value that is exported.
v := reflect.ValueOf(int8(5))
changeKind(&v, false)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(v)
s := buf.String()
want := "(int8) 5"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Dump using a reflect.Value that is not exported.
changeKind(&v, true)
buf.Reset()
d.dump(v)
s = buf.String()
want = "(int8) <int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is exported.
changeKind(&v, false)
buf2 := new(dummyFmtState)
f := formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "5"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is not exported.
changeKind(&v, true)
buf2.Reset()
f = formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "<int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
}

View file

@ -1,320 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/davecgh/go-spew/spew"
)
// spewFunc is used to identify which public function of the spew package or
// ConfigState a test applies to.
type spewFunc int
const (
fCSFdump spewFunc = iota
fCSFprint
fCSFprintf
fCSFprintln
fCSPrint
fCSPrintln
fCSSdump
fCSSprint
fCSSprintf
fCSSprintln
fCSErrorf
fCSNewFormatter
fErrorf
fFprint
fFprintln
fPrint
fPrintln
fSdump
fSprint
fSprintf
fSprintln
)
// Map of spewFunc values to names for pretty printing.
var spewFuncStrings = map[spewFunc]string{
fCSFdump: "ConfigState.Fdump",
fCSFprint: "ConfigState.Fprint",
fCSFprintf: "ConfigState.Fprintf",
fCSFprintln: "ConfigState.Fprintln",
fCSSdump: "ConfigState.Sdump",
fCSPrint: "ConfigState.Print",
fCSPrintln: "ConfigState.Println",
fCSSprint: "ConfigState.Sprint",
fCSSprintf: "ConfigState.Sprintf",
fCSSprintln: "ConfigState.Sprintln",
fCSErrorf: "ConfigState.Errorf",
fCSNewFormatter: "ConfigState.NewFormatter",
fErrorf: "spew.Errorf",
fFprint: "spew.Fprint",
fFprintln: "spew.Fprintln",
fPrint: "spew.Print",
fPrintln: "spew.Println",
fSdump: "spew.Sdump",
fSprint: "spew.Sprint",
fSprintf: "spew.Sprintf",
fSprintln: "spew.Sprintln",
}
func (f spewFunc) String() string {
if s, ok := spewFuncStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
}
// spewTest is used to describe a test to be performed against the public
// functions of the spew package or ConfigState.
type spewTest struct {
cs *spew.ConfigState
f spewFunc
format string
in interface{}
want string
}
// spewTests houses the tests to be performed against the public functions of
// the spew package and ConfigState.
//
// These tests are only intended to ensure the public functions are exercised
// and are intentionally not exhaustive of types. The exhaustive type
// tests are handled in the dump and format tests.
var spewTests []spewTest
// redirStdout is a helper function to return the standard output from f as a
// byte slice.
func redirStdout(f func()) ([]byte, error) {
tempFile, err := ioutil.TempFile("", "ss-test")
if err != nil {
return nil, err
}
fileName := tempFile.Name()
defer os.Remove(fileName) // Ignore error
origStdout := os.Stdout
os.Stdout = tempFile
f()
os.Stdout = origStdout
tempFile.Close()
return ioutil.ReadFile(fileName)
}
func initSpewTests() {
// Config states with various settings.
scsDefault := spew.NewDefaultConfig()
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
scsNoCap := &spew.ConfigState{DisableCapacities: true}
// Variables for tests on types which implement Stringer interface with and
// without a pointer receiver.
ts := stringer("test")
tps := pstringer("test")
type ptrTester struct {
s *struct{}
}
tptr := &ptrTester{s: &struct{}{}}
// depthTester is used to test max depth handling for structs, array, slices
// and maps.
type depthTester struct {
ic indirCir1
arr [1]string
slice []string
m map[string]int
}
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
map[string]int{"one": 1}}
// Variable for tests on types which implement error interface.
te := customError(10)
spewTests = []spewTest{
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
{scsDefault, fCSFprint, "", int16(32767), "32767"},
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
{scsDefault, fFprint, "", float32(3.14), "3.14"},
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
{scsDefault, fPrint, "", true, "true"},
{scsDefault, fPrintln, "", false, "false\n"},
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
{scsNoMethods, fCSFprint, "", ts, "test"},
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
{scsNoMethods, fCSFprint, "", tps, "test"},
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
{scsNoPmethods, fCSFprint, "", tps, "test"},
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
"(len=4) (stringer test) \"test\"\n"},
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
"(error: 10) 10\n"},
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
}
}
// TestSpew executes all of the tests described by spewTests.
func TestSpew(t *testing.T) {
initSpewTests()
t.Logf("Running %d tests", len(spewTests))
for i, test := range spewTests {
buf := new(bytes.Buffer)
switch test.f {
case fCSFdump:
test.cs.Fdump(buf, test.in)
case fCSFprint:
test.cs.Fprint(buf, test.in)
case fCSFprintf:
test.cs.Fprintf(buf, test.format, test.in)
case fCSFprintln:
test.cs.Fprintln(buf, test.in)
case fCSPrint:
b, err := redirStdout(func() { test.cs.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSPrintln:
b, err := redirStdout(func() { test.cs.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSSdump:
str := test.cs.Sdump(test.in)
buf.WriteString(str)
case fCSSprint:
str := test.cs.Sprint(test.in)
buf.WriteString(str)
case fCSSprintf:
str := test.cs.Sprintf(test.format, test.in)
buf.WriteString(str)
case fCSSprintln:
str := test.cs.Sprintln(test.in)
buf.WriteString(str)
case fCSErrorf:
err := test.cs.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fCSNewFormatter:
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
case fErrorf:
err := spew.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fFprint:
spew.Fprint(buf, test.in)
case fFprintln:
spew.Fprintln(buf, test.in)
case fPrint:
b, err := redirStdout(func() { spew.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fPrintln:
b, err := redirStdout(func() { spew.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fSdump:
str := spew.Sdump(test.in)
buf.WriteString(str)
case fSprint:
str := spew.Sprint(test.in)
buf.WriteString(str)
case fSprintf:
str := spew.Sprintf(test.format, test.in)
buf.WriteString(str)
case fSprintln:
str := spew.Sprintln(test.in)
buf.WriteString(str)
default:
t.Errorf("%v #%d unrecognized function", test.f, i)
continue
}
s := buf.String()
if test.want != s {
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
continue
}
}
}

View file

@ -1,11 +1,15 @@
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)
# jwt-go
[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go)
[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go)
**BREAKING CHANGES:*** Version 3.0.0 is here. It includes _a lot_ of changes including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code.
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)
**NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided.
**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3.
**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail.
**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided.
## What the heck is a JWT?
@ -47,7 +51,10 @@ This library is considered production ready. Feedback and feature requests are
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning.
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning.
**BREAKING CHANGES:***
* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code.
## Usage Tips
@ -68,6 +75,14 @@ Symmetric signing methods, such as HSA, use only a single secret. This is probab
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
### Signing Methods and Key Types
Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones:
* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
### JWT and OAuth
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.

View file

@ -1,5 +1,12 @@
## `jwt-go` Version History
#### 3.2.0
* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation
* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate
* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before.
* Deprecated `ParseFromRequestWithClaims` to simplify API in the future.
#### 3.1.0
* Improvements to `jwt` command line tool

View file

@ -14,6 +14,7 @@ var (
)
// Implements the ECDSA family of signing methods signing methods
// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
type SigningMethodECDSA struct {
Name string
Hash crypto.Hash

View file

@ -1,100 +0,0 @@
package jwt_test
import (
"crypto/ecdsa"
"io/ioutil"
"strings"
"testing"
"github.com/dgrijalva/jwt-go"
)
var ecdsaTestData = []struct {
name string
keys map[string]string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic ES256",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ",
"ES256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES384",
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld",
"ES384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES512",
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5",
"ES512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic ES256 invalid: foo => bar",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W",
"ES256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestECDSAVerify(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["public"])
var ecdsaKey *ecdsa.PublicKey
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA public key: %v", err)
}
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestECDSASign(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["private"])
var ecdsaKey *ecdsa.PrivateKey
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA private key: %v", err)
}
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] {
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
}
}
}
}

View file

@ -1,114 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"
)
// Example (atypical) using the StandardClaims type by itself to parse a token.
// The StandardClaims type is designed to be embedded into your custom types
// to provide standard validation features. You can use it alone, but there's
// no way to retrieve other fields after parsing.
// See the CustomClaimsType example for intended usage.
func ExampleNewWithClaims_standardClaims() {
mySigningKey := []byte("AllYourBase")
// Create the Claims
claims := &jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 <nil>
}
// Example creating a token using a custom claims type. The StandardClaim is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleNewWithClaims_customClaimsType() {
mySigningKey := []byte("AllYourBase")
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// Create the Claims
claims := MyCustomClaims{
"bar",
jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c <nil>
}
// Example creating a token using a custom claims type. The StandardClaim is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_customClaimsType() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// sample token is expired. override time so it parses as valid
at(time.Unix(0, 0), func() {
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt)
} else {
fmt.Println(err)
}
})
// Output: bar 15000
}
// Override time value for tests. Restore default value after.
func at(t time.Time, f func()) {
jwt.TimeFunc = func() time.Time {
return t
}
f()
jwt.TimeFunc = time.Now
}
// An example of parsing the error types using bitfield checks
func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything
}

View file

@ -7,6 +7,7 @@ import (
)
// Implements the HMAC-SHA family of signing methods signing methods
// Expects key type of []byte for both signing and validation
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
@ -90,5 +91,5 @@ func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string,
return EncodeSegment(hasher.Sum(nil)), nil
}
return "", ErrInvalidKey
return "", ErrInvalidKeyType
}

View file

@ -1,66 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"time"
)
// For HMAC signing method, the key can be any []byte. It is recommended to generate
// a key using crypto/rand or something equivalent. You need the same key for signing
// and validating.
var hmacSampleSecret []byte
func init() {
// Load sample key data
if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil {
hmacSampleSecret = keyData
} else {
panic(e)
}
}
// Example creating, signing, and encoding a JWT token using the HMAC signing method
func ExampleNew_hmac() {
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"foo": "bar",
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU <nil>
}
// Example parsing and validating a token using the HMAC signing method
func ExampleParse_hmac() {
// sample token string taken from the New example
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU"
// Parse takes the token string and a function for looking up the key. The latter is especially
// useful if you use multiple keys for your application. The standard is to use 'kid' in the
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
return hmacSampleSecret, nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
fmt.Println(claims["foo"], claims["nbf"])
} else {
fmt.Println(err)
}
// Output: bar 1.4444784e+09
}

View file

@ -1,91 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"strings"
"testing"
)
var hmacTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"web sample",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS384",
"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy",
"HS384",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS512",
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw",
"HS512",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"web sample: invalid",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
false,
},
}
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
func TestHMACVerify(t *testing.T) {
for _, data := range hmacTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestHMACSign(t *testing.T) {
for _, data := range hmacTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}
func BenchmarkHS256Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey)
}
func BenchmarkHS384Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey)
}
func BenchmarkHS512Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey)
}

View file

@ -1,216 +0,0 @@
package jwt_test
// Example HTTP auth using asymmetric crypto/RSA keys
// This is based on a (now outdated) example at https://gist.github.com/cryptix/45c33ecf0ae54828e63b
import (
"bytes"
"crypto/rsa"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// location of the files used for signing and verification
const (
privKeyPath = "test/sample_key" // openssl genrsa -out app.rsa keysize
pubKeyPath = "test/sample_key.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub
)
var (
verifyKey *rsa.PublicKey
signKey *rsa.PrivateKey
serverPort int
// storing sample username/password pairs
// don't do this on a real server
users = map[string]string{
"test": "known",
}
)
// read the key files before starting http handlers
func init() {
signBytes, err := ioutil.ReadFile(privKeyPath)
fatal(err)
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err)
verifyBytes, err := ioutil.ReadFile(pubKeyPath)
fatal(err)
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
fatal(err)
http.HandleFunc("/authenticate", authHandler)
http.HandleFunc("/restricted", restrictedHandler)
// Setup listener
listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
serverPort = listener.Addr().(*net.TCPAddr).Port
log.Println("Listening...")
go func() {
fatal(http.Serve(listener, nil))
}()
}
var start func()
func fatal(err error) {
if err != nil {
log.Fatal(err)
}
}
// Define some custom types were going to use within our tokens
type CustomerInfo struct {
Name string
Kind string
}
type CustomClaimsExample struct {
*jwt.StandardClaims
TokenType string
CustomerInfo
}
func Example_getTokenViaHTTP() {
// See func authHandler for an example auth handler that produces a token
res, err := http.PostForm(fmt.Sprintf("http://localhost:%v/authenticate", serverPort), url.Values{
"user": {"test"},
"pass": {"known"},
})
if err != nil {
fatal(err)
}
if res.StatusCode != 200 {
fmt.Println("Unexpected status code", res.StatusCode)
}
// Read the token out of the response body
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
tokenString := strings.TrimSpace(buf.String())
// Parse the token
token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return verifyKey, nil
})
fatal(err)
claims := token.Claims.(*CustomClaimsExample)
fmt.Println(claims.CustomerInfo.Name)
//Output: test
}
func Example_useTokenViaHTTP() {
// Make a sample token
// In a real world situation, this token will have been acquired from
// some other API call (see Example_getTokenViaHTTP)
token, err := createToken("foo")
fatal(err)
// Make request. See func restrictedHandler for example request processor
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%v/restricted", serverPort), nil)
fatal(err)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
res, err := http.DefaultClient.Do(req)
fatal(err)
// Read the response body
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
fmt.Println(buf.String())
// Output: Welcome, foo
}
func createToken(user string) (string, error) {
// create a signer for rsa 256
t := jwt.New(jwt.GetSigningMethod("RS256"))
// set our claims
t.Claims = &CustomClaimsExample{
&jwt.StandardClaims{
// set the expire time
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
},
"level1",
CustomerInfo{user, "human"},
}
// Creat token string
return t.SignedString(signKey)
}
// reads the form values, checks them and creates the token
func authHandler(w http.ResponseWriter, r *http.Request) {
// make sure its post
if r.Method != "POST" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "No POST", r.Method)
return
}
user := r.FormValue("user")
pass := r.FormValue("pass")
log.Printf("Authenticate: user[%s] pass[%s]\n", user, pass)
// check values
if user != "test" || pass != "known" {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintln(w, "Wrong info")
return
}
tokenString, err := createToken(user)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Sorry, error while Signing Token!")
log.Printf("Token Signing error: %v\n", err)
return
}
w.Header().Set("Content-Type", "application/jwt")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, tokenString)
}
// only accessible with a valid token
func restrictedHandler(w http.ResponseWriter, r *http.Request) {
// Get token from request
token, err := request.ParseFromRequestWithClaims(r, request.OAuth2Extractor, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return verifyKey, nil
})
// If the token is missing or invalid, return error
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(w, "Invalid token:", err)
return
}
// Token is valid
fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name)
return
}

View file

@ -1,72 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"strings"
"testing"
)
var noneTestData = []struct {
name string
tokenString string
alg string
key interface{}
claims map[string]interface{}
valid bool
}{
{
"Basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.",
"none",
jwt.UnsafeAllowNoneSignatureType,
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic - no key",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.",
"none",
nil,
map[string]interface{}{"foo": "bar"},
false,
},
{
"Signed",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"none",
jwt.UnsafeAllowNoneSignatureType,
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestNoneVerify(t *testing.T) {
for _, data := range noneTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], data.key)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestNoneSign(t *testing.T) {
for _, data := range noneTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), data.key)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}

View file

@ -21,55 +21,9 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
}
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
}
var err error
token := &Token{Raw: tokenString}
// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// parse Claims
var claimBytes []byte
token.Claims = claims
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
// JSON Decode. Special case for map type to avoid weird pointer behavior
if c, ok := token.Claims.(MapClaims); ok {
err = dec.Decode(&c)
} else {
err = dec.Decode(&claims)
}
// Handle decode error
token, parts, err := p.ParseUnverified(tokenString, claims)
if err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
}
} else {
return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
return token, err
}
// Verify signing method is in the required set
@ -96,6 +50,9 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
if ve, ok := err.(*ValidationError); ok {
return token, ve
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}
@ -129,3 +86,63 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
return token, vErr
}
// WARNING: Don't use this method unless you know what you're doing
//
// This method parses the token but doesn't validate the signature. It's only
// ever useful in cases where you know the signature is valid (because it has
// been checked previously in the stack) and you want to extract values from
// it.
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
parts = strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
}
token = &Token{Raw: tokenString}
// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
}
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// parse Claims
var claimBytes []byte
token.Claims = claims
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
// JSON Decode. Special case for map type to avoid weird pointer behavior
if c, ok := token.Claims.(MapClaims); ok {
err = dec.Decode(&c)
} else {
err = dec.Decode(&claims)
}
// Handle decode error
if err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
}
} else {
return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
}
return token, parts, nil
}

View file

@ -1,261 +0,0 @@
package jwt_test
import (
"crypto/rsa"
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/test"
)
var keyFuncError error = fmt.Errorf("error loading key")
var (
jwtTestDefaultKey *rsa.PublicKey
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil }
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, keyFuncError }
nilKeyFunc jwt.Keyfunc = nil
)
func init() {
jwtTestDefaultKey = test.LoadRSAPublicKeyFromDisk("test/sample_key.pub")
}
var jwtTestData = []struct {
name string
tokenString string
keyfunc jwt.Keyfunc
claims jwt.Claims
valid bool
errors uint32
parser *jwt.Parser
}{
{
"basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
true,
0,
nil,
},
{
"basic expired",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorExpired,
nil,
},
{
"basic nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
false,
jwt.ValidationErrorNotValidYet,
nil,
},
{
"expired and nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
nil,
},
{
"basic invalid",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
nil,
},
{
"basic nokeyfunc",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
nilKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
nil,
},
{
"basic nokey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
emptyKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
nil,
},
{
"basic errorkey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
errorKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
nil,
},
{
"invalid signing method",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
&jwt.Parser{ValidMethods: []string{"HS256"}},
},
{
"valid signing method",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
true,
0,
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
},
{
"JSON Number",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": json.Number("123.4")},
true,
0,
&jwt.Parser{UseJSONNumber: true},
},
{
"Standard Claims",
"",
defaultKeyFunc,
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
},
true,
0,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - basic expired",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false,
jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - basic nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
false,
jwt.ValidationErrorNotValidYet,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - expired and nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"SkipClaimsValidation during token parsing",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
true,
0,
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
},
}
func TestParser_Parse(t *testing.T) {
privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key")
// Iterate over test data set and run tests
for _, data := range jwtTestData {
// If the token string is blank, use helper function to generate string
if data.tokenString == "" {
data.tokenString = test.MakeSampleToken(data.claims, privateKey)
}
// Parse the token
var token *jwt.Token
var err error
var parser = data.parser
if parser == nil {
parser = new(jwt.Parser)
}
// Figure out correct claims type
switch data.claims.(type) {
case jwt.MapClaims:
token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc)
case *jwt.StandardClaims:
token, err = parser.ParseWithClaims(data.tokenString, &jwt.StandardClaims{}, data.keyfunc)
}
// Verify result matches expectation
if !reflect.DeepEqual(data.claims, token.Claims) {
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
}
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid token passed validation", data.name)
}
if (err == nil && !token.Valid) || (err != nil && token.Valid) {
t.Errorf("[%v] Inconsistent behavior between returned error and token.Valid", data.name)
}
if data.errors != 0 {
if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
} else {
ve := err.(*jwt.ValidationError)
// compare the bitfield part of the error
if e := ve.Errors; e != data.errors {
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
}
if err.Error() == keyFuncError.Error() && ve.Inner != keyFuncError {
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, keyFuncError)
}
}
}
if data.valid && token.Signature == "" {
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name)
}
}
}
// Helper method for benchmarking various methods
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) {
t := jwt.New(method)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if _, err := t.SignedString(key); err != nil {
b.Fatal(err)
}
}
})
}

View file

@ -7,6 +7,7 @@ import (
)
// Implements the RSA family of signing methods signing methods
// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation
type SigningMethodRSA struct {
Name string
Hash crypto.Hash
@ -44,7 +45,7 @@ func (m *SigningMethodRSA) Alg() string {
}
// Implements the Verify method from SigningMethod
// For this signing method, must be an rsa.PublicKey structure.
// For this signing method, must be an *rsa.PublicKey structure.
func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
var err error
@ -73,7 +74,7 @@ func (m *SigningMethodRSA) Verify(signingString, signature string, key interface
}
// Implements the Sign method from SigningMethod
// For this signing method, must be an rsa.PrivateKey structure.
// For this signing method, must be an *rsa.PrivateKey structure.
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
var ok bool

View file

@ -1,96 +0,0 @@
// +build go1.4
package jwt_test
import (
"crypto/rsa"
"io/ioutil"
"strings"
"testing"
"github.com/dgrijalva/jwt-go"
)
var rsaPSSTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic PS256",
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w",
"PS256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic PS384",
"eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ",
"PS384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic PS512",
"eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A",
"PS512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic PS256 invalid: foo => bar",
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W",
"PS256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestRSAPSSVerify(t *testing.T) {
var err error
key, _ := ioutil.ReadFile("test/sample_key.pub")
var rsaPSSKey *rsa.PublicKey
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA public key: %v", err)
}
for _, data := range rsaPSSTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestRSAPSSSign(t *testing.T) {
var err error
key, _ := ioutil.ReadFile("test/sample_key")
var rsaPSSKey *rsa.PrivateKey
if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA private key: %v", err)
}
for _, data := range rsaPSSTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), rsaPSSKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] {
t.Errorf("[%v] Signatures shouldn't match\nnew:\n%v\noriginal:\n%v", data.name, sig, parts[2])
}
}
}
}

View file

@ -1,176 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"strings"
"testing"
)
var rsaTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic RS256",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic RS384",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"RS384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic RS512",
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
"RS512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic invalid: foo => bar",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestRSAVerify(t *testing.T) {
keyData, _ := ioutil.ReadFile("test/sample_key.pub")
key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData)
for _, data := range rsaTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestRSASign(t *testing.T) {
keyData, _ := ioutil.ReadFile("test/sample_key")
key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
for _, data := range rsaTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), key)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}
func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key.pub")
parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
if err != nil {
t.Fatal(err)
}
testData := rsaTestData[0]
parts := strings.Split(testData.tokenString, ".")
err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey)
if err != nil {
t.Errorf("[%v] Error while verifying key: %v", testData.name, err)
}
}
func TestRSAWithPreParsedPrivateKey(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
t.Fatal(err)
}
testData := rsaTestData[0]
parts := strings.Split(testData.tokenString, ".")
sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", testData.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2])
}
}
func TestRSAKeyParsing(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key")
pubKey, _ := ioutil.ReadFile("test/sample_key.pub")
badKey := []byte("All your base are belong to key")
// Test parsePrivateKey
if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil {
t.Errorf("Failed to parse valid private key: %v", e)
}
if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil {
t.Errorf("Parsed public key as valid private key: %v", k)
}
if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil {
t.Errorf("Parsed invalid key as valid private key: %v", k)
}
// Test parsePublicKey
if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil {
t.Errorf("Failed to parse valid public key: %v", e)
}
if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil {
t.Errorf("Parsed private key as valid public key: %v", k)
}
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
t.Errorf("Parsed invalid key as valid private key: %v", k)
}
}
func BenchmarkRS256Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
b.Fatal(err)
}
benchmarkSigning(b, jwt.SigningMethodRS256, parsedKey)
}
func BenchmarkRS384Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
b.Fatal(err)
}
benchmarkSigning(b, jwt.SigningMethodRS384, parsedKey)
}
func BenchmarkRS512Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
b.Fatal(err)
}
benchmarkSigning(b, jwt.SigningMethodRS512, parsedKey)
}

View file

@ -39,6 +39,38 @@ func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 private key protected with password
func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var parsedKey interface{}
var blockDecrypted []byte
if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil {
return nil, err
}
if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, ErrNotRSAPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error

View file

@ -1,371 +0,0 @@
package digestset
import (
"crypto/sha256"
_ "crypto/sha512"
"encoding/binary"
"math/rand"
"testing"
digest "github.com/opencontainers/go-digest"
)
func assertEqualDigests(t *testing.T, d1, d2 digest.Digest) {
if d1 != d2 {
t.Fatalf("Digests do not match:\n\tActual: %s\n\tExpected: %s", d1, d2)
}
}
func TestLookup(t *testing.T) {
digests := []digest.Digest{
"sha256:1234511111111111111111111111111111111111111111111111111111111111",
"sha256:1234111111111111111111111111111111111111111111111111111111111111",
"sha256:1234611111111111111111111111111111111111111111111111111111111111",
"sha256:5432111111111111111111111111111111111111111111111111111111111111",
"sha256:6543111111111111111111111111111111111111111111111111111111111111",
"sha256:6432111111111111111111111111111111111111111111111111111111111111",
"sha256:6542111111111111111111111111111111111111111111111111111111111111",
"sha256:6532111111111111111111111111111111111111111111111111111111111111",
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
dgst, err := dset.Lookup("54")
if err != nil {
t.Fatal(err)
}
assertEqualDigests(t, dgst, digests[3])
dgst, err = dset.Lookup("1234")
if err == nil {
t.Fatal("Expected ambiguous error looking up: 1234")
}
if err != ErrDigestAmbiguous {
t.Fatal(err)
}
dgst, err = dset.Lookup("9876")
if err == nil {
t.Fatal("Expected ambiguous error looking up: 9876")
}
if err != ErrDigestNotFound {
t.Fatal(err)
}
dgst, err = dset.Lookup("sha256:1234")
if err == nil {
t.Fatal("Expected ambiguous error looking up: sha256:1234")
}
if err != ErrDigestAmbiguous {
t.Fatal(err)
}
dgst, err = dset.Lookup("sha256:12345")
if err != nil {
t.Fatal(err)
}
assertEqualDigests(t, dgst, digests[0])
dgst, err = dset.Lookup("sha256:12346")
if err != nil {
t.Fatal(err)
}
assertEqualDigests(t, dgst, digests[2])
dgst, err = dset.Lookup("12346")
if err != nil {
t.Fatal(err)
}
assertEqualDigests(t, dgst, digests[2])
dgst, err = dset.Lookup("12345")
if err != nil {
t.Fatal(err)
}
assertEqualDigests(t, dgst, digests[0])
}
func TestAddDuplication(t *testing.T) {
digests := []digest.Digest{
"sha256:1234111111111111111111111111111111111111111111111111111111111111",
"sha256:1234511111111111111111111111111111111111111111111111111111111111",
"sha256:1234611111111111111111111111111111111111111111111111111111111111",
"sha256:5432111111111111111111111111111111111111111111111111111111111111",
"sha256:6543111111111111111111111111111111111111111111111111111111111111",
"sha512:65431111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
"sha512:65421111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
"sha512:65321111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
if len(dset.entries) != 8 {
t.Fatal("Invalid dset size")
}
if err := dset.Add(digest.Digest("sha256:1234511111111111111111111111111111111111111111111111111111111111")); err != nil {
t.Fatal(err)
}
if len(dset.entries) != 8 {
t.Fatal("Duplicate digest insert allowed")
}
if err := dset.Add(digest.Digest("sha384:123451111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")); err != nil {
t.Fatal(err)
}
if len(dset.entries) != 9 {
t.Fatal("Insert with different algorithm not allowed")
}
}
func TestRemove(t *testing.T) {
digests, err := createDigests(10)
if err != nil {
t.Fatal(err)
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
dgst, err := dset.Lookup(digests[0].String())
if err != nil {
t.Fatal(err)
}
if dgst != digests[0] {
t.Fatalf("Unexpected digest value:\n\tExpected: %s\n\tActual: %s", digests[0], dgst)
}
if err := dset.Remove(digests[0]); err != nil {
t.Fatal(err)
}
if _, err := dset.Lookup(digests[0].String()); err != ErrDigestNotFound {
t.Fatalf("Expected error %v when looking up removed digest, got %v", ErrDigestNotFound, err)
}
}
func TestAll(t *testing.T) {
digests, err := createDigests(100)
if err != nil {
t.Fatal(err)
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
all := map[digest.Digest]struct{}{}
for _, dgst := range dset.All() {
all[dgst] = struct{}{}
}
if len(all) != len(digests) {
t.Fatalf("Unexpected number of unique digests found:\n\tExpected: %d\n\tActual: %d", len(digests), len(all))
}
for i, dgst := range digests {
if _, ok := all[dgst]; !ok {
t.Fatalf("Missing element at position %d: %s", i, dgst)
}
}
}
func assertEqualShort(t *testing.T, actual, expected string) {
if actual != expected {
t.Fatalf("Unexpected short value:\n\tExpected: %s\n\tActual: %s", expected, actual)
}
}
func TestShortCodeTable(t *testing.T) {
digests := []digest.Digest{
"sha256:1234111111111111111111111111111111111111111111111111111111111111",
"sha256:1234511111111111111111111111111111111111111111111111111111111111",
"sha256:1234611111111111111111111111111111111111111111111111111111111111",
"sha256:5432111111111111111111111111111111111111111111111111111111111111",
"sha256:6543111111111111111111111111111111111111111111111111111111111111",
"sha256:6432111111111111111111111111111111111111111111111111111111111111",
"sha256:6542111111111111111111111111111111111111111111111111111111111111",
"sha256:6532111111111111111111111111111111111111111111111111111111111111",
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
dump := ShortCodeTable(dset, 2)
if len(dump) < len(digests) {
t.Fatalf("Error unexpected size: %d, expecting %d", len(dump), len(digests))
}
assertEqualShort(t, dump[digests[0]], "12341")
assertEqualShort(t, dump[digests[1]], "12345")
assertEqualShort(t, dump[digests[2]], "12346")
assertEqualShort(t, dump[digests[3]], "54")
assertEqualShort(t, dump[digests[4]], "6543")
assertEqualShort(t, dump[digests[5]], "64")
assertEqualShort(t, dump[digests[6]], "6542")
assertEqualShort(t, dump[digests[7]], "653")
}
func createDigests(count int) ([]digest.Digest, error) {
r := rand.New(rand.NewSource(25823))
digests := make([]digest.Digest, count)
for i := range digests {
h := sha256.New()
if err := binary.Write(h, binary.BigEndian, r.Int63()); err != nil {
return nil, err
}
digests[i] = digest.NewDigest("sha256", h)
}
return digests, nil
}
func benchAddNTable(b *testing.B, n int) {
digests, err := createDigests(n)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
for j := range digests {
if err = dset.Add(digests[j]); err != nil {
b.Fatal(err)
}
}
}
}
func benchLookupNTable(b *testing.B, n int, shortLen int) {
digests, err := createDigests(n)
if err != nil {
b.Fatal(err)
}
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
b.Fatal(err)
}
}
shorts := make([]string, 0, n)
for _, short := range ShortCodeTable(dset, shortLen) {
shorts = append(shorts, short)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = dset.Lookup(shorts[i%n]); err != nil {
b.Fatal(err)
}
}
}
func benchRemoveNTable(b *testing.B, n int) {
digests, err := createDigests(n)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
b.StopTimer()
for j := range digests {
if err = dset.Add(digests[j]); err != nil {
b.Fatal(err)
}
}
b.StartTimer()
for j := range digests {
if err = dset.Remove(digests[j]); err != nil {
b.Fatal(err)
}
}
}
}
func benchShortCodeNTable(b *testing.B, n int, shortLen int) {
digests, err := createDigests(n)
if err != nil {
b.Fatal(err)
}
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
b.Fatal(err)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
ShortCodeTable(dset, shortLen)
}
}
func BenchmarkAdd10(b *testing.B) {
benchAddNTable(b, 10)
}
func BenchmarkAdd100(b *testing.B) {
benchAddNTable(b, 100)
}
func BenchmarkAdd1000(b *testing.B) {
benchAddNTable(b, 1000)
}
func BenchmarkRemove10(b *testing.B) {
benchRemoveNTable(b, 10)
}
func BenchmarkRemove100(b *testing.B) {
benchRemoveNTable(b, 100)
}
func BenchmarkRemove1000(b *testing.B) {
benchRemoveNTable(b, 1000)
}
func BenchmarkLookup10(b *testing.B) {
benchLookupNTable(b, 10, 12)
}
func BenchmarkLookup100(b *testing.B) {
benchLookupNTable(b, 100, 12)
}
func BenchmarkLookup1000(b *testing.B) {
benchLookupNTable(b, 1000, 12)
}
func BenchmarkShortCode10(b *testing.B) {
benchShortCodeNTable(b, 10, 12)
}
func BenchmarkShortCode100(b *testing.B) {
benchShortCodeNTable(b, 100, 12)
}
func BenchmarkShortCode1000(b *testing.B) {
benchShortCodeNTable(b, 1000, 12)
}

View file

@ -1,625 +0,0 @@
package reference
import (
"strconv"
"testing"
"github.com/docker/distribution/digestset"
"github.com/opencontainers/go-digest"
)
func TestValidateReferenceName(t *testing.T) {
validRepoNames := []string{
"docker/docker",
"library/debian",
"debian",
"docker.io/docker/docker",
"docker.io/library/debian",
"docker.io/debian",
"index.docker.io/docker/docker",
"index.docker.io/library/debian",
"index.docker.io/debian",
"127.0.0.1:5000/docker/docker",
"127.0.0.1:5000/library/debian",
"127.0.0.1:5000/debian",
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
// This test case was moved from invalid to valid since it is valid input
// when specified with a hostname, it removes the ambiguity from about
// whether the value is an identifier or repository name
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
}
invalidRepoNames := []string{
"https://github.com/docker/docker",
"docker/Docker",
"-docker",
"-docker/docker",
"-docker.io/docker/docker",
"docker///docker",
"docker.io/docker/Docker",
"docker.io/docker///docker",
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
}
for _, name := range invalidRepoNames {
_, err := ParseNormalizedNamed(name)
if err == nil {
t.Fatalf("Expected invalid repo name for %q", name)
}
}
for _, name := range validRepoNames {
_, err := ParseNormalizedNamed(name)
if err != nil {
t.Fatalf("Error parsing repo name %s, got: %q", name, err)
}
}
}
func TestValidateRemoteName(t *testing.T) {
validRepositoryNames := []string{
// Sanity check.
"docker/docker",
// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
// Allow embedded hyphens.
"docker-rules/docker",
// Allow multiple hyphens as well.
"docker---rules/docker",
//Username doc and image name docker being tested.
"doc/docker",
// single character names are now allowed.
"d/docker",
"jess/t",
// Consecutive underscores.
"dock__er/docker",
}
for _, repositoryName := range validRepositoryNames {
_, err := ParseNormalizedNamed(repositoryName)
if err != nil {
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
}
}
invalidRepositoryNames := []string{
// Disallow capital letters.
"docker/Docker",
// Only allow one slash.
"docker///docker",
// Disallow 64-character hexadecimal.
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
// Disallow leading and trailing hyphens in namespace.
"-docker/docker",
"docker-/docker",
"-docker-/docker",
// Don't allow underscores everywhere (as opposed to hyphens).
"____/____",
"_docker/_docker",
// Disallow consecutive periods.
"dock..er/docker",
"dock_.er/docker",
"dock-.er/docker",
// No repository.
"docker/",
//namespace too long
"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
}
for _, repositoryName := range invalidRepositoryNames {
if _, err := ParseNormalizedNamed(repositoryName); err == nil {
t.Errorf("Repository name should be invalid: %v", repositoryName)
}
}
}
func TestParseRepositoryInfo(t *testing.T) {
type tcase struct {
RemoteName, FamiliarName, FullName, AmbiguousName, Domain string
}
tcases := []tcase{
{
RemoteName: "fooo/bar",
FamiliarName: "fooo/bar",
FullName: "docker.io/fooo/bar",
AmbiguousName: "index.docker.io/fooo/bar",
Domain: "docker.io",
},
{
RemoteName: "library/ubuntu",
FamiliarName: "ubuntu",
FullName: "docker.io/library/ubuntu",
AmbiguousName: "library/ubuntu",
Domain: "docker.io",
},
{
RemoteName: "nonlibrary/ubuntu",
FamiliarName: "nonlibrary/ubuntu",
FullName: "docker.io/nonlibrary/ubuntu",
AmbiguousName: "",
Domain: "docker.io",
},
{
RemoteName: "other/library",
FamiliarName: "other/library",
FullName: "docker.io/other/library",
AmbiguousName: "",
Domain: "docker.io",
},
{
RemoteName: "private/moonbase",
FamiliarName: "127.0.0.1:8000/private/moonbase",
FullName: "127.0.0.1:8000/private/moonbase",
AmbiguousName: "",
Domain: "127.0.0.1:8000",
},
{
RemoteName: "privatebase",
FamiliarName: "127.0.0.1:8000/privatebase",
FullName: "127.0.0.1:8000/privatebase",
AmbiguousName: "",
Domain: "127.0.0.1:8000",
},
{
RemoteName: "private/moonbase",
FamiliarName: "example.com/private/moonbase",
FullName: "example.com/private/moonbase",
AmbiguousName: "",
Domain: "example.com",
},
{
RemoteName: "privatebase",
FamiliarName: "example.com/privatebase",
FullName: "example.com/privatebase",
AmbiguousName: "",
Domain: "example.com",
},
{
RemoteName: "private/moonbase",
FamiliarName: "example.com:8000/private/moonbase",
FullName: "example.com:8000/private/moonbase",
AmbiguousName: "",
Domain: "example.com:8000",
},
{
RemoteName: "privatebasee",
FamiliarName: "example.com:8000/privatebasee",
FullName: "example.com:8000/privatebasee",
AmbiguousName: "",
Domain: "example.com:8000",
},
{
RemoteName: "library/ubuntu-12.04-base",
FamiliarName: "ubuntu-12.04-base",
FullName: "docker.io/library/ubuntu-12.04-base",
AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
Domain: "docker.io",
},
{
RemoteName: "library/foo",
FamiliarName: "foo",
FullName: "docker.io/library/foo",
AmbiguousName: "docker.io/foo",
Domain: "docker.io",
},
{
RemoteName: "library/foo/bar",
FamiliarName: "library/foo/bar",
FullName: "docker.io/library/foo/bar",
AmbiguousName: "",
Domain: "docker.io",
},
{
RemoteName: "store/foo/bar",
FamiliarName: "store/foo/bar",
FullName: "docker.io/store/foo/bar",
AmbiguousName: "",
Domain: "docker.io",
},
}
for _, tcase := range tcases {
refStrings := []string{tcase.FamiliarName, tcase.FullName}
if tcase.AmbiguousName != "" {
refStrings = append(refStrings, tcase.AmbiguousName)
}
var refs []Named
for _, r := range refStrings {
named, err := ParseNormalizedNamed(r)
if err != nil {
t.Fatal(err)
}
refs = append(refs, named)
}
for _, r := range refs {
if expected, actual := tcase.FamiliarName, FamiliarName(r); expected != actual {
t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
}
if expected, actual := tcase.FullName, r.String(); expected != actual {
t.Fatalf("Invalid canonical reference for %q. Expected %q, got %q", r, expected, actual)
}
if expected, actual := tcase.Domain, Domain(r); expected != actual {
t.Fatalf("Invalid domain for %q. Expected %q, got %q", r, expected, actual)
}
if expected, actual := tcase.RemoteName, Path(r); expected != actual {
t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual)
}
}
}
}
func TestParseReferenceWithTagAndDigest(t *testing.T) {
shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
ref, err := ParseNormalizedNamed(shortRef)
if err != nil {
t.Fatal(err)
}
if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
}
if _, isTagged := ref.(NamedTagged); !isTagged {
t.Fatalf("Reference from %q should support tag", ref)
}
if _, isCanonical := ref.(Canonical); !isCanonical {
t.Fatalf("Reference from %q should support digest", ref)
}
if expected, actual := shortRef, FamiliarString(ref); actual != expected {
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
}
}
func TestInvalidReferenceComponents(t *testing.T) {
if _, err := ParseNormalizedNamed("-foo"); err == nil {
t.Fatal("Expected WithName to detect invalid name")
}
ref, err := ParseNormalizedNamed("busybox")
if err != nil {
t.Fatal(err)
}
if _, err := WithTag(ref, "-foo"); err == nil {
t.Fatal("Expected WithName to detect invalid tag")
}
if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
t.Fatal("Expected WithDigest to detect invalid digest")
}
}
func equalReference(r1, r2 Reference) bool {
switch v1 := r1.(type) {
case digestReference:
if v2, ok := r2.(digestReference); ok {
return v1 == v2
}
case repository:
if v2, ok := r2.(repository); ok {
return v1 == v2
}
case taggedReference:
if v2, ok := r2.(taggedReference); ok {
return v1 == v2
}
case canonicalReference:
if v2, ok := r2.(canonicalReference); ok {
return v1 == v2
}
case reference:
if v2, ok := r2.(reference); ok {
return v1 == v2
}
}
return false
}
func TestParseAnyReference(t *testing.T) {
tcases := []struct {
Reference string
Equivalent string
Expected Reference
Digests []digest.Digest
}{
{
Reference: "redis",
Equivalent: "docker.io/library/redis",
},
{
Reference: "redis:latest",
Equivalent: "docker.io/library/redis:latest",
},
{
Reference: "docker.io/library/redis:latest",
Equivalent: "docker.io/library/redis:latest",
},
{
Reference: "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
},
{
Reference: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
},
{
Reference: "dmcgowan/myapp",
Equivalent: "docker.io/dmcgowan/myapp",
},
{
Reference: "dmcgowan/myapp:latest",
Equivalent: "docker.io/dmcgowan/myapp:latest",
},
{
Reference: "docker.io/mcgowan/myapp:latest",
Equivalent: "docker.io/mcgowan/myapp:latest",
},
{
Reference: "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
},
{
Reference: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
},
{
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
},
{
Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
},
{
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
},
{
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Digests: []digest.Digest{
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
},
},
{
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
Digests: []digest.Digest{
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
},
},
{
Reference: "dbcc1c",
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
Digests: []digest.Digest{
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
},
},
{
Reference: "dbcc1",
Equivalent: "docker.io/library/dbcc1",
Digests: []digest.Digest{
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
},
},
{
Reference: "dbcc1c",
Equivalent: "docker.io/library/dbcc1c",
Digests: []digest.Digest{
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
},
},
}
for _, tcase := range tcases {
var ref Reference
var err error
if len(tcase.Digests) == 0 {
ref, err = ParseAnyReference(tcase.Reference)
} else {
ds := digestset.NewSet()
for _, dgst := range tcase.Digests {
if err := ds.Add(dgst); err != nil {
t.Fatalf("Error adding digest %s: %v", dgst.String(), err)
}
}
ref, err = ParseAnyReferenceWithSet(tcase.Reference, ds)
}
if err != nil {
t.Fatalf("Error parsing reference %s: %v", tcase.Reference, err)
}
if ref.String() != tcase.Equivalent {
t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tcase.Equivalent)
}
expected := tcase.Expected
if expected == nil {
expected, err = Parse(tcase.Equivalent)
if err != nil {
t.Fatalf("Error parsing reference %s: %v", tcase.Equivalent, err)
}
}
if !equalReference(ref, expected) {
t.Errorf("Unexpected reference %#v, expected %#v", ref, expected)
}
}
}
func TestNormalizedSplitHostname(t *testing.T) {
testcases := []struct {
input string
domain string
name string
}{
{
input: "test.com/foo",
domain: "test.com",
name: "foo",
},
{
input: "test_com/foo",
domain: "docker.io",
name: "test_com/foo",
},
{
input: "docker/migrator",
domain: "docker.io",
name: "docker/migrator",
},
{
input: "test.com:8080/foo",
domain: "test.com:8080",
name: "foo",
},
{
input: "test-com:8080/foo",
domain: "test-com:8080",
name: "foo",
},
{
input: "foo",
domain: "docker.io",
name: "library/foo",
},
{
input: "xn--n3h.com/foo",
domain: "xn--n3h.com",
name: "foo",
},
{
input: "xn--n3h.com:18080/foo",
domain: "xn--n3h.com:18080",
name: "foo",
},
{
input: "docker.io/foo",
domain: "docker.io",
name: "library/foo",
},
{
input: "docker.io/library/foo",
domain: "docker.io",
name: "library/foo",
},
{
input: "docker.io/library/foo/bar",
domain: "docker.io",
name: "library/foo/bar",
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail()
}
named, err := ParseNormalizedNamed(testcase.input)
if err != nil {
failf("error parsing name: %s", err)
}
domain, name := SplitHostname(named)
if domain != testcase.domain {
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
}
if name != testcase.name {
failf("unexpected name: got %q, expected %q", name, testcase.name)
}
}
}
func TestMatchError(t *testing.T) {
named, err := ParseAnyReference("foo")
if err != nil {
t.Fatal(err)
}
_, err = FamiliarMatch("[-x]", named)
if err == nil {
t.Fatalf("expected an error, got nothing")
}
}
func TestMatch(t *testing.T) {
matchCases := []struct {
reference string
pattern string
expected bool
}{
{
reference: "foo",
pattern: "foo/**/ba[rz]",
expected: false,
},
{
reference: "foo/any/bat",
pattern: "foo/**/ba[rz]",
expected: false,
},
{
reference: "foo/a/bar",
pattern: "foo/**/ba[rz]",
expected: true,
},
{
reference: "foo/b/baz",
pattern: "foo/**/ba[rz]",
expected: true,
},
{
reference: "foo/c/baz:tag",
pattern: "foo/**/ba[rz]",
expected: true,
},
{
reference: "foo/c/baz:tag",
pattern: "foo/*/baz:tag",
expected: true,
},
{
reference: "foo/c/baz:tag",
pattern: "foo/c/baz:tag",
expected: true,
},
{
reference: "example.com/foo/c/baz:tag",
pattern: "*/foo/c/baz",
expected: true,
},
{
reference: "example.com/foo/c/baz:tag",
pattern: "example.com/foo/c/baz",
expected: true,
},
}
for _, c := range matchCases {
named, err := ParseAnyReference(c.reference)
if err != nil {
t.Fatal(err)
}
actual, err := FamiliarMatch(c.pattern, named)
if err != nil {
t.Fatal(err)
}
if actual != c.expected {
t.Fatalf("expected %s match %s to be %v, was %v", c.reference, c.pattern, c.expected, actual)
}
}
}

View file

@ -1,659 +0,0 @@
package reference
import (
_ "crypto/sha256"
_ "crypto/sha512"
"encoding/json"
"strconv"
"strings"
"testing"
"github.com/opencontainers/go-digest"
)
func TestReferenceParse(t *testing.T) {
// referenceTestcases is a unified set of testcases for
// testing the parsing of references
referenceTestcases := []struct {
// input is the repository name or name component testcase
input string
// err is the error expected from Parse, or nil
err error
// repository is the string representation for the reference
repository string
// domain is the domain expected in the reference
domain string
// tag is the tag for the reference
tag string
// digest is the digest for the reference (enforces digest reference)
digest string
}{
{
input: "test_com",
repository: "test_com",
},
{
input: "test.com:tag",
repository: "test.com",
tag: "tag",
},
{
input: "test.com:5000",
repository: "test.com",
tag: "5000",
},
{
input: "test.com/repo:tag",
domain: "test.com",
repository: "test.com/repo",
tag: "tag",
},
{
input: "test:5000/repo",
domain: "test:5000",
repository: "test:5000/repo",
},
{
input: "test:5000/repo:tag",
domain: "test:5000",
repository: "test:5000/repo",
tag: "tag",
},
{
input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
domain: "test:5000",
repository: "test:5000/repo",
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
},
{
input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
domain: "test:5000",
repository: "test:5000/repo",
tag: "tag",
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
},
{
input: "test:5000/repo",
domain: "test:5000",
repository: "test:5000/repo",
},
{
input: "",
err: ErrNameEmpty,
},
{
input: ":justtag",
err: ErrReferenceInvalidFormat,
},
{
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
err: ErrReferenceInvalidFormat,
},
{
input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
err: digest.ErrDigestInvalidLength,
},
{
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
err: digest.ErrDigestUnsupported,
},
{
input: "Uppercase:tag",
err: ErrNameContainsUppercase,
},
// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
// See https://github.com/docker/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
//{
// input: "Uppercase/lowercase:tag",
// err: ErrNameContainsUppercase,
//},
{
input: "test:5000/Uppercase/lowercase:tag",
err: ErrNameContainsUppercase,
},
{
input: "lowercase:Uppercase",
repository: "lowercase",
tag: "Uppercase",
},
{
input: strings.Repeat("a/", 128) + "a:tag",
err: ErrNameTooLong,
},
{
input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
domain: "a",
repository: strings.Repeat("a/", 127) + "a",
tag: "tag-puts-this-over-max",
},
{
input: "aa/asdf$$^/aa",
err: ErrReferenceInvalidFormat,
},
{
input: "sub-dom1.foo.com/bar/baz/quux",
domain: "sub-dom1.foo.com",
repository: "sub-dom1.foo.com/bar/baz/quux",
},
{
input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
domain: "sub-dom1.foo.com",
repository: "sub-dom1.foo.com/bar/baz/quux",
tag: "some-long-tag",
},
{
input: "b.gcr.io/test.example.com/my-app:test.example.com",
domain: "b.gcr.io",
repository: "b.gcr.io/test.example.com/my-app",
tag: "test.example.com",
},
{
input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
domain: "xn--n3h.com",
repository: "xn--n3h.com/myimage",
tag: "xn--n3h.com",
},
{
input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
domain: "xn--7o8h.com",
repository: "xn--7o8h.com/myimage",
tag: "xn--7o8h.com",
digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
},
{
input: "foo_bar.com:8080",
repository: "foo_bar.com",
tag: "8080",
},
{
input: "foo/foo_bar.com:8080",
domain: "foo",
repository: "foo/foo_bar.com",
tag: "8080",
},
}
for _, testcase := range referenceTestcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail()
}
repo, err := Parse(testcase.input)
if testcase.err != nil {
if err == nil {
failf("missing expected error: %v", testcase.err)
} else if testcase.err != err {
failf("mismatched error: got %v, expected %v", err, testcase.err)
}
continue
} else if err != nil {
failf("unexpected parse error: %v", err)
continue
}
if repo.String() != testcase.input {
failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input)
}
if named, ok := repo.(Named); ok {
if named.Name() != testcase.repository {
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository)
}
domain, _ := SplitHostname(named)
if domain != testcase.domain {
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
}
} else if testcase.repository != "" || testcase.domain != "" {
failf("expected named type, got %T", repo)
}
tagged, ok := repo.(Tagged)
if testcase.tag != "" {
if ok {
if tagged.Tag() != testcase.tag {
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
}
} else {
failf("expected tagged type, got %T", repo)
}
} else if ok {
failf("unexpected tagged type")
}
digested, ok := repo.(Digested)
if testcase.digest != "" {
if ok {
if digested.Digest().String() != testcase.digest {
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
}
} else {
failf("expected digested type, got %T", repo)
}
} else if ok {
failf("unexpected digested type")
}
}
}
// TestWithNameFailure tests cases where WithName should fail. Cases where it
// should succeed are covered by TestSplitHostname, below.
func TestWithNameFailure(t *testing.T) {
testcases := []struct {
input string
err error
}{
{
input: "",
err: ErrNameEmpty,
},
{
input: ":justtag",
err: ErrReferenceInvalidFormat,
},
{
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
err: ErrReferenceInvalidFormat,
},
{
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
err: ErrReferenceInvalidFormat,
},
{
input: strings.Repeat("a/", 128) + "a:tag",
err: ErrNameTooLong,
},
{
input: "aa/asdf$$^/aa",
err: ErrReferenceInvalidFormat,
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail()
}
_, err := WithName(testcase.input)
if err == nil {
failf("no error parsing name. expected: %s", testcase.err)
}
}
}
func TestSplitHostname(t *testing.T) {
testcases := []struct {
input string
domain string
name string
}{
{
input: "test.com/foo",
domain: "test.com",
name: "foo",
},
{
input: "test_com/foo",
domain: "",
name: "test_com/foo",
},
{
input: "test:8080/foo",
domain: "test:8080",
name: "foo",
},
{
input: "test.com:8080/foo",
domain: "test.com:8080",
name: "foo",
},
{
input: "test-com:8080/foo",
domain: "test-com:8080",
name: "foo",
},
{
input: "xn--n3h.com:18080/foo",
domain: "xn--n3h.com:18080",
name: "foo",
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail()
}
named, err := WithName(testcase.input)
if err != nil {
failf("error parsing name: %s", err)
}
domain, name := SplitHostname(named)
if domain != testcase.domain {
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
}
if name != testcase.name {
failf("unexpected name: got %q, expected %q", name, testcase.name)
}
}
}
type serializationType struct {
Description string
Field Field
}
func TestSerialization(t *testing.T) {
testcases := []struct {
description string
input string
name string
tag string
digest string
err error
}{
{
description: "empty value",
err: ErrNameEmpty,
},
{
description: "just a name",
input: "example.com:8000/named",
name: "example.com:8000/named",
},
{
description: "name with a tag",
input: "example.com:8000/named:tagged",
name: "example.com:8000/named",
tag: "tagged",
},
{
description: "name with digest",
input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
name: "other.com/named",
digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112",
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail()
}
m := map[string]string{
"Description": testcase.description,
"Field": testcase.input,
}
b, err := json.Marshal(m)
if err != nil {
failf("error marshalling: %v", err)
}
t := serializationType{}
if err := json.Unmarshal(b, &t); err != nil {
if testcase.err == nil {
failf("error unmarshalling: %v", err)
}
if err != testcase.err {
failf("wrong error, expected %v, got %v", testcase.err, err)
}
continue
} else if testcase.err != nil {
failf("expected error unmarshalling: %v", testcase.err)
}
if t.Description != testcase.description {
failf("wrong description, expected %q, got %q", testcase.description, t.Description)
}
ref := t.Field.Reference()
if named, ok := ref.(Named); ok {
if named.Name() != testcase.name {
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name)
}
} else if testcase.name != "" {
failf("expected named type, got %T", ref)
}
tagged, ok := ref.(Tagged)
if testcase.tag != "" {
if ok {
if tagged.Tag() != testcase.tag {
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
}
} else {
failf("expected tagged type, got %T", ref)
}
} else if ok {
failf("unexpected tagged type")
}
digested, ok := ref.(Digested)
if testcase.digest != "" {
if ok {
if digested.Digest().String() != testcase.digest {
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
}
} else {
failf("expected digested type, got %T", ref)
}
} else if ok {
failf("unexpected digested type")
}
t = serializationType{
Description: testcase.description,
Field: AsField(ref),
}
b2, err := json.Marshal(t)
if err != nil {
failf("error marshing serialization type: %v", err)
}
if string(b) != string(b2) {
failf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
}
// Ensure t.Field is not implementing "Reference" directly, getting
// around the Reference type system
var fieldInterface interface{} = t.Field
if _, ok := fieldInterface.(Reference); ok {
failf("field should not implement Reference interface")
}
}
}
func TestWithTag(t *testing.T) {
testcases := []struct {
name string
digest digest.Digest
tag string
combined string
}{
{
name: "test.com/foo",
tag: "tag",
combined: "test.com/foo:tag",
},
{
name: "foo",
tag: "tag2",
combined: "foo:tag2",
},
{
name: "test.com:8000/foo",
tag: "tag4",
combined: "test.com:8000/foo:tag4",
},
{
name: "test.com:8000/foo",
tag: "TAG5",
combined: "test.com:8000/foo:TAG5",
},
{
name: "test.com:8000/foo",
digest: "sha256:1234567890098765432112345667890098765",
tag: "TAG5",
combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
t.Fail()
}
named, err := WithName(testcase.name)
if err != nil {
failf("error parsing name: %s", err)
}
if testcase.digest != "" {
canonical, err := WithDigest(named, testcase.digest)
if err != nil {
failf("error adding digest")
}
named = canonical
}
tagged, err := WithTag(named, testcase.tag)
if err != nil {
failf("WithTag failed: %s", err)
}
if tagged.String() != testcase.combined {
failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined)
}
}
}
func TestWithDigest(t *testing.T) {
testcases := []struct {
name string
digest digest.Digest
tag string
combined string
}{
{
name: "test.com/foo",
digest: "sha256:1234567890098765432112345667890098765",
combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
},
{
name: "foo",
digest: "sha256:1234567890098765432112345667890098765",
combined: "foo@sha256:1234567890098765432112345667890098765",
},
{
name: "test.com:8000/foo",
digest: "sha256:1234567890098765432112345667890098765",
combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
},
{
name: "test.com:8000/foo",
digest: "sha256:1234567890098765432112345667890098765",
tag: "latest",
combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
t.Fail()
}
named, err := WithName(testcase.name)
if err != nil {
failf("error parsing name: %s", err)
}
if testcase.tag != "" {
tagged, err := WithTag(named, testcase.tag)
if err != nil {
failf("error adding tag")
}
named = tagged
}
digested, err := WithDigest(named, testcase.digest)
if err != nil {
failf("WithDigest failed: %s", err)
}
if digested.String() != testcase.combined {
failf("unexpected: got %q, expected %q", digested.String(), testcase.combined)
}
}
}
func TestParseNamed(t *testing.T) {
testcases := []struct {
input string
domain string
name string
err error
}{
{
input: "test.com/foo",
domain: "test.com",
name: "foo",
},
{
input: "test:8080/foo",
domain: "test:8080",
name: "foo",
},
{
input: "test_com/foo",
err: ErrNameNotCanonical,
},
{
input: "test.com",
err: ErrNameNotCanonical,
},
{
input: "foo",
err: ErrNameNotCanonical,
},
{
input: "library/foo",
err: ErrNameNotCanonical,
},
{
input: "docker.io/library/foo",
domain: "docker.io",
name: "library/foo",
},
// Ambiguous case, parser will add "library/" to foo
{
input: "docker.io/foo",
err: ErrNameNotCanonical,
},
}
for _, testcase := range testcases {
failf := func(format string, v ...interface{}) {
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
t.Fail()
}
named, err := ParseNamed(testcase.input)
if err != nil && testcase.err == nil {
failf("error parsing name: %s", err)
continue
} else if err == nil && testcase.err != nil {
failf("parsing succeded: expected error %v", testcase.err)
continue
} else if err != testcase.err {
failf("unexpected error %v, expected %v", err, testcase.err)
continue
} else if err != nil {
continue
}
domain, name := SplitHostname(named)
if domain != testcase.domain {
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
}
if name != testcase.name {
failf("unexpected name: got %q, expected %q", name, testcase.name)
}
}
}

View file

@ -1,553 +0,0 @@
package reference
import (
"regexp"
"strings"
"testing"
)
type regexpMatch struct {
input string
match bool
subs []string
}
func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
matches := r.FindStringSubmatch(m.input)
if m.match && matches != nil {
if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
t.Fatalf("Bad match result %#v for %q", matches, m.input)
}
if len(matches) < (len(m.subs) + 1) {
t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
}
for i := range m.subs {
if m.subs[i] != matches[i+1] {
t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
}
}
} else if m.match {
t.Errorf("Expected match for %q", m.input)
} else if matches != nil {
t.Errorf("Unexpected match for %q", m.input)
}
}
func TestDomainRegexp(t *testing.T) {
hostcases := []regexpMatch{
{
input: "test.com",
match: true,
},
{
input: "test.com:10304",
match: true,
},
{
input: "test.com:http",
match: false,
},
{
input: "localhost",
match: true,
},
{
input: "localhost:8080",
match: true,
},
{
input: "a",
match: true,
},
{
input: "a.b",
match: true,
},
{
input: "ab.cd.com",
match: true,
},
{
input: "a-b.com",
match: true,
},
{
input: "-ab.com",
match: false,
},
{
input: "ab-.com",
match: false,
},
{
input: "ab.c-om",
match: true,
},
{
input: "ab.-com",
match: false,
},
{
input: "ab.com-",
match: false,
},
{
input: "0101.com",
match: true, // TODO(dmcgowan): valid if this should be allowed
},
{
input: "001a.com",
match: true,
},
{
input: "b.gbc.io:443",
match: true,
},
{
input: "b.gbc.io",
match: true,
},
{
input: "xn--n3h.com", // ☃.com in punycode
match: true,
},
{
input: "Asdf.com", // uppercase character
match: true,
},
}
r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
for i := range hostcases {
checkRegexp(t, r, hostcases[i])
}
}
func TestFullNameRegexp(t *testing.T) {
if anchoredNameRegexp.NumSubexp() != 2 {
t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
}
testcases := []regexpMatch{
{
input: "",
match: false,
},
{
input: "short",
match: true,
subs: []string{"", "short"},
},
{
input: "simple/name",
match: true,
subs: []string{"simple", "name"},
},
{
input: "library/ubuntu",
match: true,
subs: []string{"library", "ubuntu"},
},
{
input: "docker/stevvooe/app",
match: true,
subs: []string{"docker", "stevvooe/app"},
},
{
input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
match: true,
subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
},
{
input: "aa/aa/bb/bb/bb",
match: true,
subs: []string{"aa", "aa/bb/bb/bb"},
},
{
input: "a/a/a/a",
match: true,
subs: []string{"a", "a/a/a"},
},
{
input: "a/a/a/a/",
match: false,
},
{
input: "a//a/a",
match: false,
},
{
input: "a",
match: true,
subs: []string{"", "a"},
},
{
input: "a/aa",
match: true,
subs: []string{"a", "aa"},
},
{
input: "a/aa/a",
match: true,
subs: []string{"a", "aa/a"},
},
{
input: "foo.com",
match: true,
subs: []string{"", "foo.com"},
},
{
input: "foo.com/",
match: false,
},
{
input: "foo.com:8080/bar",
match: true,
subs: []string{"foo.com:8080", "bar"},
},
{
input: "foo.com:http/bar",
match: false,
},
{
input: "foo.com/bar",
match: true,
subs: []string{"foo.com", "bar"},
},
{
input: "foo.com/bar/baz",
match: true,
subs: []string{"foo.com", "bar/baz"},
},
{
input: "localhost:8080/bar",
match: true,
subs: []string{"localhost:8080", "bar"},
},
{
input: "sub-dom1.foo.com/bar/baz/quux",
match: true,
subs: []string{"sub-dom1.foo.com", "bar/baz/quux"},
},
{
input: "blog.foo.com/bar/baz",
match: true,
subs: []string{"blog.foo.com", "bar/baz"},
},
{
input: "a^a",
match: false,
},
{
input: "aa/asdf$$^/aa",
match: false,
},
{
input: "asdf$$^/aa",
match: false,
},
{
input: "aa-a/a",
match: true,
subs: []string{"aa-a", "a"},
},
{
input: strings.Repeat("a/", 128) + "a",
match: true,
subs: []string{"a", strings.Repeat("a/", 127) + "a"},
},
{
input: "a-/a/a/a",
match: false,
},
{
input: "foo.com/a-/a/a",
match: false,
},
{
input: "-foo/bar",
match: false,
},
{
input: "foo/bar-",
match: false,
},
{
input: "foo-/bar",
match: false,
},
{
input: "foo/-bar",
match: false,
},
{
input: "_foo/bar",
match: false,
},
{
input: "foo_bar",
match: true,
subs: []string{"", "foo_bar"},
},
{
input: "foo_bar.com",
match: true,
subs: []string{"", "foo_bar.com"},
},
{
input: "foo_bar.com:8080",
match: false,
},
{
input: "foo_bar.com:8080/app",
match: false,
},
{
input: "foo.com/foo_bar",
match: true,
subs: []string{"foo.com", "foo_bar"},
},
{
input: "____/____",
match: false,
},
{
input: "_docker/_docker",
match: false,
},
{
input: "docker_/docker_",
match: false,
},
{
input: "b.gcr.io/test.example.com/my-app",
match: true,
subs: []string{"b.gcr.io", "test.example.com/my-app"},
},
{
input: "xn--n3h.com/myimage", // ☃.com in punycode
match: true,
subs: []string{"xn--n3h.com", "myimage"},
},
{
input: "xn--7o8h.com/myimage", // 🐳.com in punycode
match: true,
subs: []string{"xn--7o8h.com", "myimage"},
},
{
input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
match: true,
subs: []string{"example.com", "xn--7o8h.com/myimage"},
},
{
input: "example.com/some_separator__underscore/myimage",
match: true,
subs: []string{"example.com", "some_separator__underscore/myimage"},
},
{
input: "example.com/__underscore/myimage",
match: false,
},
{
input: "example.com/..dots/myimage",
match: false,
},
{
input: "example.com/.dots/myimage",
match: false,
},
{
input: "example.com/nodouble..dots/myimage",
match: false,
},
{
input: "example.com/nodouble..dots/myimage",
match: false,
},
{
input: "docker./docker",
match: false,
},
{
input: ".docker/docker",
match: false,
},
{
input: "docker-/docker",
match: false,
},
{
input: "-docker/docker",
match: false,
},
{
input: "do..cker/docker",
match: false,
},
{
input: "do__cker:8080/docker",
match: false,
},
{
input: "do__cker/docker",
match: true,
subs: []string{"", "do__cker/docker"},
},
{
input: "b.gcr.io/test.example.com/my-app",
match: true,
subs: []string{"b.gcr.io", "test.example.com/my-app"},
},
{
input: "registry.io/foo/project--id.module--name.ver---sion--name",
match: true,
subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
},
{
input: "Asdf.com/foo/bar", // uppercase character in hostname
match: true,
},
{
input: "Foo/FarB", // uppercase characters in remote name
match: false,
},
}
for i := range testcases {
checkRegexp(t, anchoredNameRegexp, testcases[i])
}
}
func TestReferenceRegexp(t *testing.T) {
if ReferenceRegexp.NumSubexp() != 3 {
t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
ReferenceRegexp, ReferenceRegexp.NumSubexp())
}
testcases := []regexpMatch{
{
input: "registry.com:8080/myapp:tag",
match: true,
subs: []string{"registry.com:8080/myapp", "tag", ""},
},
{
input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
match: true,
subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
},
{
input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
match: true,
subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
},
{
input: "registry.com:8080/myapp@sha256:badbadbadbad",
match: false,
},
{
input: "registry.com:8080/myapp:invalid~tag",
match: false,
},
{
input: "bad_hostname.com:8080/myapp:tag",
match: false,
},
{
input:// localhost treated as name, missing tag with 8080 as tag
"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
match: true,
subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
},
{
input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
match: true,
subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
},
{
input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
match: false,
},
{
// localhost will be treated as an image name without a host
input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
match: true,
subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
},
{
input: "registry.com:8080/myapp@bad",
match: false,
},
{
input: "registry.com:8080/myapp@2bad",
match: false, // TODO(dmcgowan): Support this as valid
},
}
for i := range testcases {
checkRegexp(t, ReferenceRegexp, testcases[i])
}
}
func TestIdentifierRegexp(t *testing.T) {
fullCases := []regexpMatch{
{
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
match: true,
},
{
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
match: false,
},
{
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
match: false,
},
{
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
match: false,
},
{
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
match: false,
},
}
shortCases := []regexpMatch{
{
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
match: true,
},
{
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
match: false,
},
{
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
match: true,
},
{
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
match: false,
},
{
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
match: false,
},
{
input: "da304",
match: false,
},
{
input: "da304e",
match: true,
},
}
for i := range fullCases {
checkRegexp(t, anchoredIdentifierRegexp, fullCases[i])
}
for i := range shortCases {
checkRegexp(t, anchoredShortIdentifierRegexp, shortCases[i])
}
}

View file

@ -1,108 +0,0 @@
package spdystream
import (
"sync"
"testing"
"time"
"github.com/docker/spdystream/spdy"
)
func TestPriorityQueueOrdering(t *testing.T) {
queue := NewPriorityFrameQueue(150)
data1 := &spdy.DataFrame{}
data2 := &spdy.DataFrame{}
data3 := &spdy.DataFrame{}
data4 := &spdy.DataFrame{}
queue.Push(data1, 2)
queue.Push(data2, 1)
queue.Push(data3, 1)
queue.Push(data4, 0)
if queue.Pop() != data4 {
t.Fatalf("Wrong order, expected data4 first")
}
if queue.Pop() != data2 {
t.Fatalf("Wrong order, expected data2 second")
}
if queue.Pop() != data3 {
t.Fatalf("Wrong order, expected data3 third")
}
if queue.Pop() != data1 {
t.Fatalf("Wrong order, expected data1 fourth")
}
// Insert 50 Medium priority frames
for i := spdy.StreamId(50); i < 100; i++ {
queue.Push(&spdy.DataFrame{StreamId: i}, 1)
}
// Insert 50 low priority frames
for i := spdy.StreamId(100); i < 150; i++ {
queue.Push(&spdy.DataFrame{StreamId: i}, 2)
}
// Insert 50 high priority frames
for i := spdy.StreamId(0); i < 50; i++ {
queue.Push(&spdy.DataFrame{StreamId: i}, 0)
}
for i := spdy.StreamId(0); i < 150; i++ {
frame := queue.Pop()
if frame.(*spdy.DataFrame).StreamId != i {
t.Fatalf("Wrong frame\nActual: %d\nExpecting: %d", frame.(*spdy.DataFrame).StreamId, i)
}
}
}
func TestPriorityQueueSync(t *testing.T) {
queue := NewPriorityFrameQueue(150)
var wg sync.WaitGroup
insertRange := func(start, stop spdy.StreamId, priority uint8) {
for i := start; i < stop; i++ {
queue.Push(&spdy.DataFrame{StreamId: i}, priority)
}
wg.Done()
}
wg.Add(3)
go insertRange(spdy.StreamId(100), spdy.StreamId(150), 2)
go insertRange(spdy.StreamId(0), spdy.StreamId(50), 0)
go insertRange(spdy.StreamId(50), spdy.StreamId(100), 1)
wg.Wait()
for i := spdy.StreamId(0); i < 150; i++ {
frame := queue.Pop()
if frame.(*spdy.DataFrame).StreamId != i {
t.Fatalf("Wrong frame\nActual: %d\nExpecting: %d", frame.(*spdy.DataFrame).StreamId, i)
}
}
}
func TestPriorityQueueBlocking(t *testing.T) {
queue := NewPriorityFrameQueue(15)
for i := 0; i < 15; i++ {
queue.Push(&spdy.DataFrame{}, 2)
}
doneChan := make(chan bool)
go func() {
queue.Push(&spdy.DataFrame{}, 2)
close(doneChan)
}()
select {
case <-doneChan:
t.Fatalf("Push succeeded, expected to block")
case <-time.After(time.Millisecond):
break
}
queue.Pop()
select {
case <-doneChan:
break
case <-time.After(time.Millisecond):
t.Fatalf("Push should have succeeded, but timeout reached")
}
for i := 0; i < 15; i++ {
queue.Pop()
}
}

View file

@ -1,644 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package spdy
import (
"bytes"
"compress/zlib"
"encoding/base64"
"io"
"io/ioutil"
"net/http"
"reflect"
"testing"
)
var HeadersFixture = http.Header{
"Url": []string{"http://www.google.com/"},
"Method": []string{"get"},
"Version": []string{"http/1.1"},
}
func TestHeaderParsing(t *testing.T) {
var headerValueBlockBuf bytes.Buffer
writeHeaderValueBlock(&headerValueBlockBuf, HeadersFixture)
const bogusStreamId = 1
newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId)
if err != nil {
t.Fatal("parseHeaderValueBlock:", err)
}
if !reflect.DeepEqual(HeadersFixture, newHeaders) {
t.Fatal("got: ", newHeaders, "\nwant: ", HeadersFixture)
}
}
func TestCreateParseSynStreamFrameCompressionDisable(t *testing.T) {
buffer := new(bytes.Buffer)
// Fixture framer for no compression test.
framer := &Framer{
headerCompressionDisabled: true,
w: buffer,
headerBuf: new(bytes.Buffer),
r: buffer,
}
synStreamFrame := SynStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynStream,
},
StreamId: 2,
Headers: HeadersFixture,
}
if err := framer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame without compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame without compression:", err)
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}
func TestCreateParseSynStreamFrameCompressionEnable(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
synStreamFrame := SynStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynStream,
},
StreamId: 2,
Headers: HeadersFixture,
}
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
if err := framer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame with compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame with compression:", err)
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}
func TestCreateParseSynReplyFrameCompressionDisable(t *testing.T) {
buffer := new(bytes.Buffer)
framer := &Framer{
headerCompressionDisabled: true,
w: buffer,
headerBuf: new(bytes.Buffer),
r: buffer,
}
synReplyFrame := SynReplyFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynReply,
},
StreamId: 2,
Headers: HeadersFixture,
}
if err := framer.WriteFrame(&synReplyFrame); err != nil {
t.Fatal("WriteFrame without compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame without compression:", err)
}
parsedSynReplyFrame, ok := frame.(*SynReplyFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
}
}
func TestCreateParseSynReplyFrameCompressionEnable(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
synReplyFrame := SynReplyFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynReply,
},
StreamId: 2,
Headers: HeadersFixture,
}
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
if err := framer.WriteFrame(&synReplyFrame); err != nil {
t.Fatal("WriteFrame with compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame with compression:", err)
}
parsedSynReplyFrame, ok := frame.(*SynReplyFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
}
}
func TestCreateParseRstStream(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
rstStreamFrame := RstStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeRstStream,
},
StreamId: 1,
Status: InvalidStream,
}
if err := framer.WriteFrame(&rstStreamFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedRstStreamFrame, ok := frame.(*RstStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(rstStreamFrame, *parsedRstStreamFrame) {
t.Fatal("got: ", *parsedRstStreamFrame, "\nwant: ", rstStreamFrame)
}
}
func TestCreateParseSettings(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
settingsFrame := SettingsFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSettings,
},
FlagIdValues: []SettingsFlagIdValue{
{FlagSettingsPersistValue, SettingsCurrentCwnd, 10},
{FlagSettingsPersisted, SettingsUploadBandwidth, 1},
},
}
if err := framer.WriteFrame(&settingsFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedSettingsFrame, ok := frame.(*SettingsFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(settingsFrame, *parsedSettingsFrame) {
t.Fatal("got: ", *parsedSettingsFrame, "\nwant: ", settingsFrame)
}
}
func TestCreateParsePing(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
pingFrame := PingFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypePing,
},
Id: 31337,
}
if err := framer.WriteFrame(&pingFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
if pingFrame.CFHeader.Flags != 0 {
t.Fatal("Incorrect frame type:", pingFrame)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedPingFrame, ok := frame.(*PingFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if parsedPingFrame.CFHeader.Flags != 0 {
t.Fatal("Parsed incorrect frame type:", parsedPingFrame)
}
if !reflect.DeepEqual(pingFrame, *parsedPingFrame) {
t.Fatal("got: ", *parsedPingFrame, "\nwant: ", pingFrame)
}
}
func TestCreateParseGoAway(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
goAwayFrame := GoAwayFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeGoAway,
},
LastGoodStreamId: 31337,
Status: 1,
}
if err := framer.WriteFrame(&goAwayFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
if goAwayFrame.CFHeader.Flags != 0 {
t.Fatal("Incorrect frame type:", goAwayFrame)
}
if goAwayFrame.CFHeader.length != 8 {
t.Fatal("Incorrect frame type:", goAwayFrame)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedGoAwayFrame, ok := frame.(*GoAwayFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if parsedGoAwayFrame.CFHeader.Flags != 0 {
t.Fatal("Incorrect frame type:", parsedGoAwayFrame)
}
if parsedGoAwayFrame.CFHeader.length != 8 {
t.Fatal("Incorrect frame type:", parsedGoAwayFrame)
}
if !reflect.DeepEqual(goAwayFrame, *parsedGoAwayFrame) {
t.Fatal("got: ", *parsedGoAwayFrame, "\nwant: ", goAwayFrame)
}
}
func TestCreateParseHeadersFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer := &Framer{
headerCompressionDisabled: true,
w: buffer,
headerBuf: new(bytes.Buffer),
r: buffer,
}
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
StreamId: 2,
}
headersFrame.Headers = HeadersFixture
if err := framer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame without compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame without compression:", err)
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
}
func TestCreateParseHeadersFrameCompressionEnable(t *testing.T) {
buffer := new(bytes.Buffer)
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
StreamId: 2,
}
headersFrame.Headers = HeadersFixture
framer, err := NewFramer(buffer, buffer)
if err := framer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame with compression:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame with compression:", err)
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
}
func TestCreateParseWindowUpdateFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
windowUpdateFrame := WindowUpdateFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeWindowUpdate,
},
StreamId: 31337,
DeltaWindowSize: 1,
}
if err := framer.WriteFrame(&windowUpdateFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
if windowUpdateFrame.CFHeader.Flags != 0 {
t.Fatal("Incorrect frame type:", windowUpdateFrame)
}
if windowUpdateFrame.CFHeader.length != 8 {
t.Fatal("Incorrect frame type:", windowUpdateFrame)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedWindowUpdateFrame, ok := frame.(*WindowUpdateFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if parsedWindowUpdateFrame.CFHeader.Flags != 0 {
t.Fatal("Incorrect frame type:", parsedWindowUpdateFrame)
}
if parsedWindowUpdateFrame.CFHeader.length != 8 {
t.Fatal("Incorrect frame type:", parsedWindowUpdateFrame)
}
if !reflect.DeepEqual(windowUpdateFrame, *parsedWindowUpdateFrame) {
t.Fatal("got: ", *parsedWindowUpdateFrame, "\nwant: ", windowUpdateFrame)
}
}
func TestCreateParseDataFrame(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
dataFrame := DataFrame{
StreamId: 1,
Data: []byte{'h', 'e', 'l', 'l', 'o'},
}
if err := framer.WriteFrame(&dataFrame); err != nil {
t.Fatal("WriteFrame:", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame:", err)
}
parsedDataFrame, ok := frame.(*DataFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(dataFrame, *parsedDataFrame) {
t.Fatal("got: ", *parsedDataFrame, "\nwant: ", dataFrame)
}
}
func TestCompressionContextAcrossFrames(t *testing.T) {
buffer := new(bytes.Buffer)
framer, err := NewFramer(buffer, buffer)
if err != nil {
t.Fatal("Failed to create new framer:", err)
}
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
StreamId: 2,
Headers: HeadersFixture,
}
if err := framer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame (HEADERS):", err)
}
synStreamFrame := SynStreamFrame{
ControlFrameHeader{
Version,
TypeSynStream,
0, // Flags
0, // length
},
2, // StreamId
0, // AssociatedTOStreamID
0, // Priority
1, // Slot
nil, // Headers
}
synStreamFrame.Headers = HeadersFixture
if err := framer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame (SYN_STREAM):", err)
}
frame, err := framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (HEADERS):", err, buffer.Bytes())
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatalf("expected HeadersFrame; got %T %v", frame, frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
frame, err = framer.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (SYN_STREAM):", err, buffer.Bytes())
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatalf("expected SynStreamFrame; got %T %v", frame, frame)
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}
func TestMultipleSPDYFrames(t *testing.T) {
// Initialize the framers.
pr1, pw1 := io.Pipe()
pr2, pw2 := io.Pipe()
writer, err := NewFramer(pw1, pr2)
if err != nil {
t.Fatal("Failed to create writer:", err)
}
reader, err := NewFramer(pw2, pr1)
if err != nil {
t.Fatal("Failed to create reader:", err)
}
// Set up the frames we're actually transferring.
headersFrame := HeadersFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeHeaders,
},
StreamId: 2,
Headers: HeadersFixture,
}
synStreamFrame := SynStreamFrame{
CFHeader: ControlFrameHeader{
version: Version,
frameType: TypeSynStream,
},
StreamId: 2,
Headers: HeadersFixture,
}
// Start the goroutines to write the frames.
go func() {
if err := writer.WriteFrame(&headersFrame); err != nil {
t.Fatal("WriteFrame (HEADERS): ", err)
}
if err := writer.WriteFrame(&synStreamFrame); err != nil {
t.Fatal("WriteFrame (SYN_STREAM): ", err)
}
}()
// Read the frames and verify they look as expected.
frame, err := reader.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (HEADERS): ", err)
}
parsedHeadersFrame, ok := frame.(*HeadersFrame)
if !ok {
t.Fatal("Parsed incorrect frame type:", frame)
}
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
}
frame, err = reader.ReadFrame()
if err != nil {
t.Fatal("ReadFrame (SYN_STREAM):", err)
}
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
if !ok {
t.Fatal("Parsed incorrect frame type.")
}
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
}
}
func TestReadMalformedZlibHeader(t *testing.T) {
// These were constructed by corrupting the first byte of the zlib
// header after writing.
malformedStructs := map[string]string{
"SynStreamFrame": "gAIAAQAAABgAAAACAAAAAAAAF/nfolGyYmAAAAAA//8=",
"SynReplyFrame": "gAIAAgAAABQAAAACAAAX+d+iUbJiYAAAAAD//w==",
"HeadersFrame": "gAIACAAAABQAAAACAAAX+d+iUbJiYAAAAAD//w==",
}
for name, bad := range malformedStructs {
b, err := base64.StdEncoding.DecodeString(bad)
if err != nil {
t.Errorf("Unable to decode base64 encoded frame %s: %v", name, err)
}
buf := bytes.NewBuffer(b)
reader, err := NewFramer(buf, buf)
if err != nil {
t.Fatalf("NewFramer: %v", err)
}
_, err = reader.ReadFrame()
if err != zlib.ErrHeader {
t.Errorf("Frame %s, expected: %#v, actual: %#v", name, zlib.ErrHeader, err)
}
}
}
// TODO: these tests are too weak for updating SPDY spec. Fix me.
type zeroStream struct {
frame Frame
encoded string
}
var streamIdZeroFrames = map[string]zeroStream{
"SynStreamFrame": {
&SynStreamFrame{StreamId: 0},
"gAIAAQAAABgAAAAAAAAAAAAAePnfolGyYmAAAAAA//8=",
},
"SynReplyFrame": {
&SynReplyFrame{StreamId: 0},
"gAIAAgAAABQAAAAAAAB4+d+iUbJiYAAAAAD//w==",
},
"RstStreamFrame": {
&RstStreamFrame{StreamId: 0},
"gAIAAwAAAAgAAAAAAAAAAA==",
},
"HeadersFrame": {
&HeadersFrame{StreamId: 0},
"gAIACAAAABQAAAAAAAB4+d+iUbJiYAAAAAD//w==",
},
"DataFrame": {
&DataFrame{StreamId: 0},
"AAAAAAAAAAA=",
},
"PingFrame": {
&PingFrame{Id: 0},
"gAIABgAAAAQAAAAA",
},
}
func TestNoZeroStreamId(t *testing.T) {
t.Log("skipping") // TODO: update to work with SPDY3
return
for name, f := range streamIdZeroFrames {
b, err := base64.StdEncoding.DecodeString(f.encoded)
if err != nil {
t.Errorf("Unable to decode base64 encoded frame %s: %v", f, err)
continue
}
framer, err := NewFramer(ioutil.Discard, bytes.NewReader(b))
if err != nil {
t.Fatalf("NewFramer: %v", err)
}
err = framer.WriteFrame(f.frame)
checkZeroStreamId(t, name, "WriteFrame", err)
_, err = framer.ReadFrame()
checkZeroStreamId(t, name, "ReadFrame", err)
}
}
func checkZeroStreamId(t *testing.T, frame string, method string, err error) {
if err == nil {
t.Errorf("%s ZeroStreamId, no error on %s", method, frame)
return
}
eerr, ok := err.(*Error)
if !ok || eerr.Err != ZeroStreamId {
t.Errorf("%s ZeroStreamId, incorrect error %#v, frame %s", method, eerr, frame)
}
}

View file

@ -1,113 +0,0 @@
package spdystream
import (
"fmt"
"io"
"net"
"net/http"
"sync"
"testing"
)
func configureServer() (io.Closer, string, *sync.WaitGroup) {
authenticated = true
wg := &sync.WaitGroup{}
server, listen, serverErr := runServer(wg)
if serverErr != nil {
panic(serverErr)
}
return server, listen, wg
}
func BenchmarkDial10000(b *testing.B) {
server, addr, wg := configureServer()
defer func() {
server.Close()
wg.Wait()
}()
for i := 0; i < b.N; i++ {
conn, dialErr := net.Dial("tcp", addr)
if dialErr != nil {
panic(fmt.Sprintf("Error dialing server: %s", dialErr))
}
conn.Close()
}
}
func BenchmarkDialWithSPDYStream10000(b *testing.B) {
server, addr, wg := configureServer()
defer func() {
server.Close()
wg.Wait()
}()
for i := 0; i < b.N; i++ {
conn, dialErr := net.Dial("tcp", addr)
if dialErr != nil {
b.Fatalf("Error dialing server: %s", dialErr)
}
spdyConn, spdyErr := NewConnection(conn, false)
if spdyErr != nil {
b.Fatalf("Error creating spdy connection: %s", spdyErr)
}
go spdyConn.Serve(NoOpStreamHandler)
closeErr := spdyConn.Close()
if closeErr != nil {
b.Fatalf("Error closing connection: %s, closeErr")
}
}
}
func benchmarkStreamWithDataAndSize(size uint64, b *testing.B) {
server, addr, wg := configureServer()
defer func() {
server.Close()
wg.Wait()
}()
for i := 0; i < b.N; i++ {
conn, dialErr := net.Dial("tcp", addr)
if dialErr != nil {
b.Fatalf("Error dialing server: %s", dialErr)
}
spdyConn, spdyErr := NewConnection(conn, false)
if spdyErr != nil {
b.Fatalf("Error creating spdy connection: %s", spdyErr)
}
go spdyConn.Serve(MirrorStreamHandler)
stream, err := spdyConn.CreateStream(http.Header{}, nil, false)
writer := make([]byte, size)
stream.Write(writer)
if err != nil {
panic(err)
}
reader := make([]byte, size)
stream.Read(reader)
stream.Close()
closeErr := spdyConn.Close()
if closeErr != nil {
b.Fatalf("Error closing connection: %s, closeErr")
}
}
}
func BenchmarkStreamWith1Byte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1, b) }
func BenchmarkStreamWith1KiloByte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1024, b) }
func BenchmarkStreamWith1Megabyte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1024*1024, b) }

File diff suppressed because it is too large Load diff

View file

@ -1,45 +0,0 @@
package channels
import "testing"
func testBatches(t *testing.T, ch Channel) {
go func() {
for i := 0; i < 1000; i++ {
ch.In() <- i
}
ch.Close()
}()
i := 0
for val := range ch.Out() {
for _, elem := range val.([]interface{}) {
if i != elem.(int) {
t.Fatal("batching channel expected", i, "but got", elem.(int))
}
i++
}
}
}
func TestBatchingChannel(t *testing.T) {
ch := NewBatchingChannel(Infinity)
testBatches(t, ch)
ch = NewBatchingChannel(2)
testBatches(t, ch)
ch = NewBatchingChannel(1)
testChannelConcurrentAccessors(t, "batching channel", ch)
}
func TestBatchingChannelCap(t *testing.T) {
ch := NewBatchingChannel(Infinity)
if ch.Cap() != Infinity {
t.Error("incorrect capacity on infinite channel")
}
ch = NewBatchingChannel(5)
if ch.Cap() != 5 {
t.Error("incorrect capacity on infinite channel")
}
}

View file

@ -1,26 +0,0 @@
package channels
import "testing"
func TestBlackHole(t *testing.T) {
discard := NewBlackHole()
for i := 0; i < 1000; i++ {
discard.In() <- i
}
discard.Close()
if discard.Len() != 1000 {
t.Error("blackhole expected 1000 was", discard.Len())
}
// no asserts here, this is just for the race detector's benefit
ch := NewBlackHole()
go ch.Len()
go ch.Cap()
go func() {
ch.In() <- nil
}()
}

View file

@ -1,265 +0,0 @@
package channels
import (
"math/rand"
"testing"
"time"
)
func testChannel(t *testing.T, name string, ch Channel) {
go func() {
for i := 0; i < 1000; i++ {
ch.In() <- i
}
ch.Close()
}()
for i := 0; i < 1000; i++ {
val := <-ch.Out()
if i != val.(int) {
t.Fatal(name, "expected", i, "but got", val.(int))
}
}
}
func testChannelPair(t *testing.T, name string, in InChannel, out OutChannel) {
go func() {
for i := 0; i < 1000; i++ {
in.In() <- i
}
in.Close()
}()
for i := 0; i < 1000; i++ {
val := <-out.Out()
if i != val.(int) {
t.Fatal("pair", name, "expected", i, "but got", val.(int))
}
}
}
func testChannelConcurrentAccessors(t *testing.T, name string, ch Channel) {
// no asserts here, this is just for the race detector's benefit
go ch.Len()
go ch.Cap()
go func() {
ch.In() <- nil
}()
go func() {
<-ch.Out()
}()
}
func TestPipe(t *testing.T) {
a := NewNativeChannel(None)
b := NewNativeChannel(None)
Pipe(a, b)
testChannelPair(t, "pipe", a, b)
}
func TestWeakPipe(t *testing.T) {
a := NewNativeChannel(None)
b := NewNativeChannel(None)
WeakPipe(a, b)
testChannelPair(t, "pipe", a, b)
}
func testMultiplex(t *testing.T, multi func(output SimpleInChannel, inputs ...SimpleOutChannel)) {
a := NewNativeChannel(None)
b := NewNativeChannel(None)
multi(b, a)
testChannelPair(t, "simple multiplex", a, b)
a = NewNativeChannel(None)
inputs := []Channel{
NewNativeChannel(None),
NewNativeChannel(None),
NewNativeChannel(None),
NewNativeChannel(None),
}
multi(a, inputs[0], inputs[1], inputs[2], inputs[3])
go func() {
rand.Seed(time.Now().Unix())
for i := 0; i < 1000; i++ {
inputs[rand.Intn(len(inputs))].In() <- i
}
for i := range inputs {
inputs[i].Close()
}
}()
for i := 0; i < 1000; i++ {
val := <-a.Out()
if i != val.(int) {
t.Fatal("multiplexing expected", i, "but got", val.(int))
}
}
}
func TestMultiplex(t *testing.T) {
testMultiplex(t, Multiplex)
}
func TestWeakMultiplex(t *testing.T) {
testMultiplex(t, WeakMultiplex)
}
func testTee(t *testing.T, tee func(input SimpleOutChannel, outputs ...SimpleInChannel)) {
a := NewNativeChannel(None)
b := NewNativeChannel(None)
tee(a, b)
testChannelPair(t, "simple tee", a, b)
a = NewNativeChannel(None)
outputs := []Channel{
NewNativeChannel(None),
NewNativeChannel(None),
NewNativeChannel(None),
NewNativeChannel(None),
}
tee(a, outputs[0], outputs[1], outputs[2], outputs[3])
go func() {
for i := 0; i < 1000; i++ {
a.In() <- i
}
a.Close()
}()
for i := 0; i < 1000; i++ {
for _, output := range outputs {
val := <-output.Out()
if i != val.(int) {
t.Fatal("teeing expected", i, "but got", val.(int))
}
}
}
}
func TestTee(t *testing.T) {
testTee(t, Tee)
}
func TestWeakTee(t *testing.T) {
testTee(t, WeakTee)
}
func testDistribute(t *testing.T, dist func(input SimpleOutChannel, outputs ...SimpleInChannel)) {
a := NewNativeChannel(None)
b := NewNativeChannel(None)
dist(a, b)
testChannelPair(t, "simple distribute", a, b)
a = NewNativeChannel(None)
outputs := []Channel{
NewNativeChannel(None),
NewNativeChannel(None),
NewNativeChannel(None),
NewNativeChannel(None),
}
dist(a, outputs[0], outputs[1], outputs[2], outputs[3])
go func() {
for i := 0; i < 1000; i++ {
a.In() <- i
}
a.Close()
}()
received := make([]bool, 1000)
for _ = range received {
var val interface{}
select {
case val = <-outputs[0].Out():
case val = <-outputs[1].Out():
case val = <-outputs[2].Out():
case val = <-outputs[3].Out():
}
if received[val.(int)] {
t.Fatal("distribute got value twice", val.(int))
}
received[val.(int)] = true
}
for i := range received {
if !received[i] {
t.Fatal("distribute missed", i)
}
}
}
func TestDistribute(t *testing.T) {
testDistribute(t, Distribute)
}
func TestWeakDistribute(t *testing.T) {
testDistribute(t, WeakDistribute)
}
func TestWrap(t *testing.T) {
rawChan := make(chan int, 5)
ch := Wrap(rawChan)
for i := 0; i < 5; i++ {
rawChan <- i
}
close(rawChan)
for i := 0; i < 5; i++ {
x := (<-ch.Out()).(int)
if x != i {
t.Error("Wrapped value", x, "was expecting", i)
}
}
_, ok := <-ch.Out()
if ok {
t.Error("Wrapped channel didn't close")
}
}
func TestUnwrap(t *testing.T) {
rawChan := make(chan int)
ch := NewNativeChannel(5)
Unwrap(ch, rawChan)
for i := 0; i < 5; i++ {
ch.In() <- i
}
ch.Close()
for i := 0; i < 5; i++ {
x := <-rawChan
if x != i {
t.Error("Unwrapped value", x, "was expecting", i)
}
}
_, ok := <-rawChan
if ok {
t.Error("Unwrapped channel didn't close")
}
}
func ExampleChannel() {
var ch Channel
ch = NewInfiniteChannel()
for i := 0; i < 10; i++ {
ch.In() <- nil
}
for i := 0; i < 10; i++ {
<-ch.Out()
}
}

View file

@ -1,48 +0,0 @@
package channels
import "testing"
func TestInfiniteChannel(t *testing.T) {
var ch Channel
ch = NewInfiniteChannel()
testChannel(t, "infinite channel", ch)
ch = NewInfiniteChannel()
testChannelPair(t, "infinite channel", ch, ch)
ch = NewInfiniteChannel()
testChannelConcurrentAccessors(t, "infinite channel", ch)
}
func BenchmarkInfiniteChannelSerial(b *testing.B) {
ch := NewInfiniteChannel()
for i := 0; i < b.N; i++ {
ch.In() <- nil
}
for i := 0; i < b.N; i++ {
<-ch.Out()
}
}
func BenchmarkInfiniteChannelParallel(b *testing.B) {
ch := NewInfiniteChannel()
go func() {
for i := 0; i < b.N; i++ {
<-ch.Out()
}
ch.Close()
}()
for i := 0; i < b.N; i++ {
ch.In() <- nil
}
<-ch.Out()
}
func BenchmarkInfiniteChannelTickTock(b *testing.B) {
ch := NewInfiniteChannel()
for i := 0; i < b.N; i++ {
ch.In() <- nil
<-ch.Out()
}
}

View file

@ -1,58 +0,0 @@
package channels
import "testing"
func TestNativeChannels(t *testing.T) {
var ch Channel
ch = NewNativeChannel(None)
testChannel(t, "bufferless native channel", ch)
ch = NewNativeChannel(None)
testChannelPair(t, "bufferless native channel", ch, ch)
ch = NewNativeChannel(5)
testChannel(t, "5-buffer native channel", ch)
ch = NewNativeChannel(5)
testChannelPair(t, "5-buffer native channel", ch, ch)
ch = NewNativeChannel(None)
testChannelConcurrentAccessors(t, "native channel", ch)
}
func TestNativeInOutChannels(t *testing.T) {
ch1 := make(chan interface{})
ch2 := make(chan interface{})
Pipe(NativeOutChannel(ch1), NativeInChannel(ch2))
NativeInChannel(ch1).Close()
}
func TestDeadChannel(t *testing.T) {
ch := NewDeadChannel()
if ch.Len() != 0 {
t.Error("dead channel length not 0")
}
if ch.Cap() != 0 {
t.Error("dead channel cap not 0")
}
select {
case <-ch.Out():
t.Error("read from a dead channel")
default:
}
select {
case ch.In() <- nil:
t.Error("wrote to a dead channel")
default:
}
ch.Close()
ch = NewDeadChannel()
testChannelConcurrentAccessors(t, "dead channel", ch)
}

View file

@ -1,49 +0,0 @@
package channels
import "testing"
func TestOverflowingChannel(t *testing.T) {
var ch Channel
ch = NewOverflowingChannel(Infinity) // yes this is rather silly, but it should work
testChannel(t, "infinite overflowing channel", ch)
ch = NewOverflowingChannel(None)
go func() {
for i := 0; i < 1000; i++ {
ch.In() <- i
}
ch.Close()
}()
prev := -1
for i := range ch.Out() {
if prev >= i.(int) {
t.Fatal("overflowing channel prev", prev, "but got", i.(int))
}
}
ch = NewOverflowingChannel(10)
for i := 0; i < 1000; i++ {
ch.In() <- i
}
ch.Close()
for i := 0; i < 10; i++ {
val := <-ch.Out()
if i != val.(int) {
t.Fatal("overflowing channel expected", i, "but got", val.(int))
}
}
if val, open := <-ch.Out(); open == true {
t.Fatal("overflowing channel expected closed but got", val)
}
ch = NewOverflowingChannel(None)
ch.In() <- 0
ch.Close()
if val, open := <-ch.Out(); open == true {
t.Fatal("overflowing channel expected closed but got", val)
}
ch = NewOverflowingChannel(2)
testChannelConcurrentAccessors(t, "overflowing channel", ch)
}

Some files were not shown because too many files have changed in this diff Show more