Update vendoring from go mod. (#5566)
This commit is contained in:
parent
bfb42c34a8
commit
b43800125c
|
@ -0,0 +1,15 @@
|
|||
# This is the official list of cloud authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as:
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Google Inc.
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Palm Stone Games, Inc.
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
|
@ -0,0 +1,40 @@
|
|||
# People who have agreed to one of the CLAs and can contribute patches.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# https://developers.google.com/open-source/cla/individual
|
||||
# https://developers.google.com/open-source/cla/corporate
|
||||
#
|
||||
# Names should be added to this file as:
|
||||
# Name <email address>
|
||||
|
||||
# Keep the list alphabetically sorted.
|
||||
|
||||
Alexis Hunt <lexer@google.com>
|
||||
Andreas Litt <andreas.litt@gmail.com>
|
||||
Andrew Gerrand <adg@golang.org>
|
||||
Brad Fitzpatrick <bradfitz@golang.org>
|
||||
Burcu Dogan <jbd@google.com>
|
||||
Dave Day <djd@golang.org>
|
||||
David Sansome <me@davidsansome.com>
|
||||
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>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Sarah Adams <shadams@google.com>
|
||||
Thanatat Tamtan <acoshift@gmail.com>
|
||||
Toby Burress <kurin@google.com>
|
||||
Tuo Shan <shantuo@google.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
|
@ -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.
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
// This example demonstrates how to use your own transport when using this package.
|
||||
func ExampleNewClient() {
|
||||
c := metadata.NewClient(&http.Client{Transport: userAgentTransport{
|
||||
userAgent: "my-user-agent",
|
||||
base: http.DefaultTransport,
|
||||
}})
|
||||
p, err := c.ProjectID()
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
_ = p // TODO: Use p.
|
||||
}
|
||||
|
||||
// userAgentTransport sets the User-Agent header before calling base.
|
||||
type userAgentTransport struct {
|
||||
userAgent string
|
||||
base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface.
|
||||
func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("User-Agent", t.userAgent)
|
||||
return t.base.RoundTrip(req)
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideUserAgent(t *testing.T) {
|
||||
const userAgent = "my-user-agent"
|
||||
rt := &rrt{}
|
||||
c := NewClient(&http.Client{Transport: userAgentTransport{userAgent, rt}})
|
||||
c.Get("foo")
|
||||
if got, want := rt.gotUserAgent, userAgent; got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type userAgentTransport struct {
|
||||
userAgent string
|
||||
base http.RoundTripper
|
||||
}
|
||||
|
||||
func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("User-Agent", t.userAgent)
|
||||
return t.base.RoundTrip(req)
|
||||
}
|
||||
|
||||
type rrt struct {
|
||||
gotUserAgent string
|
||||
}
|
||||
|
||||
func (r *rrt) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
r.gotUserAgent = req.Header.Get("User-Agent")
|
||||
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
Thank you for your contribution to Go-AutoRest! We will triage and review it as soon as we can.
|
||||
|
||||
As part of submitting, please make sure you can make the following assertions:
|
||||
- [ ] I've tested my changes, adding unit tests if applicable.
|
||||
- [ ] I've added Apache 2.0 Headers to the top of any new source files.
|
||||
- [ ] I'm submitting this PR to the `dev` branch, except in the case of urgent bug fixes warranting their own release.
|
||||
- [ ] If I'm targeting `master`, I've updated [CHANGELOG.md](https://github.com/Azure/go-autorest/blob/master/CHANGELOG.md) to address the changes I'm making.
|
|
@ -1,31 +0,0 @@
|
|||
# The standard Go .gitignore file follows. (Sourced from: github.com/github/gitignore/master/Go.gitignore)
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.DS_Store
|
||||
.idea/
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# go-autorest specific
|
||||
vendor/
|
||||
autorest/azure/example/example
|
|
@ -1,35 +0,0 @@
|
|||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- master
|
||||
- 1.11.x
|
||||
- 1.10.x
|
||||
- 1.9.x
|
||||
- 1.8.x
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
|
||||
env:
|
||||
- DEP_VERSION="0.5.0"
|
||||
|
||||
before_install:
|
||||
- curl -L -o $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v$DEP_VERSION/dep-linux-amd64 && chmod +x $GOPATH/bin/dep
|
||||
|
||||
install:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- go get -u github.com/stretchr/testify
|
||||
- go get -u github.com/GoASTScanner/gas
|
||||
- dep ensure
|
||||
|
||||
script:
|
||||
- grep -L -r --include *.go --exclude-dir vendor -P "Copyright (\d{4}|\(c\)) Microsoft" ./ | tee /dev/stderr | test -z "$(< /dev/stdin)"
|
||||
- if [[ $TRAVIS_GO_VERSION == 1.11* ]]; then test -z "$(gofmt -s -l -w ./autorest/. | tee /dev/stderr)"; fi
|
||||
- test -z "$(golint ./autorest/... | tee /dev/stderr)"
|
||||
- go vet ./autorest/...
|
||||
- test -z "$(gas ./autorest/... | tee /dev/stderr | grep Error)"
|
||||
- go build -v ./autorest/...
|
||||
- go test -v ./autorest/...
|
|
@ -1,587 +0,0 @@
|
|||
# CHANGELOG
|
||||
|
||||
## v10.15.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Initialize the polling URL and method for an LRO tracker on each iteration, favoring the Azure-AsyncOperation header.
|
||||
|
||||
## v10.15.2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use fmt.Fprint when printing request/response so that any escape sequences aren't treated as format specifiers.
|
||||
|
||||
## v10.15.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- If an LRO API returns a ```Failed``` provisioning state in the initial response return an error at that point so the caller doesn't have to poll.
|
||||
- For failed LROs without an OData v4 error include the response body in the error's ```AdditionalInfo``` field to aid in diagnosing the failure.
|
||||
|
||||
## v10.15.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Add initial support for request/response logging via setting environment variables.
|
||||
Setting ```AZURE_GO_SDK_LOG_LEVEL``` to ```LogInfo``` will log request/response
|
||||
without their bodies. To include the bodies set the log level to ```LogDebug```.
|
||||
By default the logger writes to strerr, however it can also write to stdout or a file
|
||||
if specified in ```AZURE_GO_SDK_LOG_FILE```. Note that if the specified file
|
||||
already exists it will be truncated.
|
||||
IMPORTANT: by default the logger will redact the Authorization and Ocp-Apim-Subscription-Key
|
||||
headers. Any other secrets will *not* be redacted.
|
||||
|
||||
## v10.14.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added package version that contains version constants and user-agent data.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add the user-agent to token requests.
|
||||
|
||||
## v10.13.0
|
||||
|
||||
- Added support for additionalInfo in ServiceError type.
|
||||
|
||||
## v10.12.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added field ServicePrincipalToken.MaxMSIRefreshAttempts to configure the maximun number of attempts to refresh an MSI token.
|
||||
|
||||
## v10.11.4
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- If an LRO returns http.StatusOK on the initial response with no async headers return the response body from Future.GetResult().
|
||||
- If there is no "final GET URL" return an error from Future.GetResult().
|
||||
|
||||
## v10.11.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- In IMDS retry logic, if we don't receive a response don't retry.
|
||||
- Renamed the retry function so it's clear it's meant for IMDS only.
|
||||
- For error response bodies that aren't OData-v4 compliant stick the raw JSON in the ServiceError.Details field so the information isn't lost.
|
||||
- Also add the raw HTTP response to the DetailedResponse.
|
||||
- Removed superfluous wrapping of response error in azure.DoRetryWithRegistration().
|
||||
|
||||
## v10.11.2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Validation for integers handles int and int64 types.
|
||||
|
||||
## v10.11.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Adding User information to authorization config as parsed from CLI cache.
|
||||
|
||||
## v10.11.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added NewServicePrincipalTokenFromManualTokenSecret for creating a new SPT using a manual token and secret
|
||||
- Added method ServicePrincipalToken.MarshalTokenJSON() to marshall the inner Token
|
||||
|
||||
## v10.10.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Most ServicePrincipalTokens can now be marshalled/unmarshall to/from JSON (ServicePrincipalCertificateSecret and ServicePrincipalMSISecret are not supported).
|
||||
- Added method ServicePrincipalToken.SetRefreshCallbacks().
|
||||
|
||||
## v10.9.2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Refreshing a refresh token obtained from a web app authorization code now works.
|
||||
|
||||
## v10.9.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- The retry logic for MSI token requests now uses exponential backoff per the guidelines.
|
||||
- IsTemporaryNetworkError() will return true for errors that don't implement the net.Error interface.
|
||||
|
||||
## v10.9.0
|
||||
|
||||
### Deprecated Methods
|
||||
|
||||
| Old Method | New Method |
|
||||
|-------------:|:-----------:|
|
||||
|azure.NewFuture() | azure.NewFutureFromResponse()|
|
||||
|Future.WaitForCompletion() | Future.WaitForCompletionRef()|
|
||||
|
||||
### New Features
|
||||
|
||||
- Added azure.NewFutureFromResponse() for creating a Future from the initial response from an async operation.
|
||||
- Added Future.GetResult() for making the final GET call to retrieve the result from an async operation.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Some futures failed to return their results, this should now be fixed.
|
||||
|
||||
## v10.8.2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add nil-gaurd to token retry logic.
|
||||
|
||||
## v10.8.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Return a TokenRefreshError if the sender fails on the initial request.
|
||||
- Don't retry on non-temporary network errors.
|
||||
|
||||
## v10.8.0
|
||||
|
||||
- Added NewAuthorizerFromEnvironmentWithResource() helper function.
|
||||
|
||||
## v10.7.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added *WithContext() methods to ADAL token refresh operations.
|
||||
|
||||
## v10.6.2
|
||||
|
||||
- Fixed a bug on device authentication.
|
||||
|
||||
## v10.6.1
|
||||
|
||||
- Added retries to MSI token get request.
|
||||
|
||||
## v10.6.0
|
||||
|
||||
- Changed MSI token implementation. Now, the token endpoint is the IMDS endpoint.
|
||||
|
||||
## v10.5.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- `DeviceFlowConfig.Authorizer()` now prints the device code message when running `go test`. `-v` flag is required.
|
||||
|
||||
## v10.5.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added NewPollingRequestWithContext() for use with polling asynchronous operations.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make retry logic use the request's context instead of the deprecated Cancel object.
|
||||
|
||||
## v10.4.0
|
||||
|
||||
### New Features
|
||||
- Added helper for parsing Azure Resource ID's.
|
||||
- Added deprecation message to utils.GetEnvVarOrExit()
|
||||
|
||||
## v10.3.0
|
||||
|
||||
### New Features
|
||||
- Added EnvironmentFromURL method to load an Environment from a given URL. This function is particularly useful in the private and hybrid Cloud model, where one may define their own endpoints
|
||||
- Added TokenAudience endpoint to Environment structure. This is useful in private and hybrid cloud models where TokenAudience endpoint can be different from ResourceManagerEndpoint
|
||||
|
||||
## v10.2.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added endpoints for batch management.
|
||||
|
||||
## v10.1.3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- In Client.Do() invoke WithInspection() last so that it will inspect WithAuthorization().
|
||||
- Fixed authorization methods to invoke p.Prepare() first, aligning them with the other preparers.
|
||||
|
||||
## v10.1.2
|
||||
|
||||
- Corrected comment for auth.NewAuthorizerFromFile() function.
|
||||
|
||||
## v10.1.1
|
||||
|
||||
- Updated version number to match current release.
|
||||
|
||||
## v10.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Expose the polling URL for futures.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add validation.NewErrorWithValidationError back to prevent breaking changes (it is deprecated).
|
||||
|
||||
## v10.0.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added target and innererror fields to ServiceError to comply with OData v4 spec.
|
||||
- The Done() method on futures will now return a ServiceError object when available (it used to return a partial value of such errors).
|
||||
- Added helper methods for obtaining authorizers.
|
||||
- Expose the polling URL for futures.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Switched from glide to dep for dependency management.
|
||||
- Fixed unmarshaling of ServiceError for JSON bodies that don't conform to the OData spec.
|
||||
- Fixed a race condition in token refresh.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- The ServiceError.Details field type has been changed to match the OData v4 spec.
|
||||
- Go v1.7 has been dropped from CI.
|
||||
- API parameter validation failures will now return a unique error type validation.Error.
|
||||
- The adal.Token type has been decomposed from adal.ServicePrincipalToken (this was necessary in order to fix the token refresh race).
|
||||
|
||||
## v9.10.0
|
||||
- Fix the Service Bus suffix in Azure public env
|
||||
- Add Service Bus Endpoint (AAD ResourceURI) for use in [Azure Service Bus RBAC Preview](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-role-based-access-control)
|
||||
|
||||
## 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
|
||||
|
||||
- Added WaitForCompletion() to Future as a default polling implementation.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Method Future.Done() shouldn't update polling status for unexpected HTTP status codes.
|
||||
|
||||
## v9.3.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- DoRetryForStatusCodes will retry if sender.Do returns a non-nil error.
|
||||
|
||||
## v9.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added PollingMethod() to Future so callers know what kind of polling mechanism is used.
|
||||
- Added azure.ChangeToGet() which transforms an http.Request into a GET (to be used with LROs).
|
||||
|
||||
## v9.2.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Added support for custom Azure Stack endpoints.
|
||||
- Added type azure.Future used to track the status of long-running operations.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Preserve the original error in DoRetryWithRegistration when registration fails.
|
||||
|
||||
## v9.1.1
|
||||
|
||||
- Fixes a bug regarding the cookie jar on `autorest.Client.Sender`.
|
||||
|
||||
## v9.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
- In cases where there is a non-empty error from the service, attempt to unmarshal it instead of uniformly calling it an "Unknown" error.
|
||||
- Support for loading Azure CLI Authentication files.
|
||||
- Automatically register your subscription with the Azure Resource Provider if it hadn't been previously.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- RetriableRequest can now tolerate a ReadSeekable body being read but not reset.
|
||||
- Adding missing Apache Headers
|
||||
|
||||
## v9.0.0
|
||||
|
||||
> **IMPORTANT:** This release was intially labeled incorrectly as `v8.4.0`. From the time it was released, it should have been marked `v9.0.0` because it contains breaking changes to the MSI packages. We appologize for any inconvenience this causes.
|
||||
|
||||
Adding MSI Endpoint Support and CLI token rehydration.
|
||||
|
||||
## v8.3.1
|
||||
|
||||
Pick up bug fix in adal for MSI support.
|
||||
|
||||
## v8.3.0
|
||||
|
||||
Updates to Error string formats for clarity. Also, adding a copy of the http.Response to errors for an improved debugging experience.
|
||||
|
||||
## v8.2.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Add support for bearer authentication callbacks
|
||||
- Support 429 response codes that include "Retry-After" header
|
||||
- Support validation constraint "Pattern" for map keys
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make RetriableRequest work with multiple versions of Go
|
||||
|
||||
## v8.1.1
|
||||
Updates the RetriableRequest to take advantage of GetBody() added in Go 1.8.
|
||||
|
||||
## v8.1.0
|
||||
Adds RetriableRequest type for more efficient handling of retrying HTTP requests.
|
||||
|
||||
## v8.0.0
|
||||
|
||||
ADAL refactored into its own package.
|
||||
Support for UNIX time.
|
||||
|
||||
## v7.3.1
|
||||
- Version Testing now removed from production bits that are shipped with the library.
|
||||
|
||||
## v7.3.0
|
||||
- Exposing new `RespondDecorator`, `ByDiscardingBody`. This allows operations
|
||||
to acknowledge that they do not need either the entire or a trailing portion
|
||||
of accepts response body. In doing so, Go's http library can reuse HTTP
|
||||
connections more readily.
|
||||
- Adding `PrepareDecorator` to target custom BaseURLs.
|
||||
- Adding ACR suffix to public cloud environment.
|
||||
- Updating Glide dependencies.
|
||||
|
||||
## v7.2.5
|
||||
- Fixed the Active Directory endpoint for the China cloud.
|
||||
- Removes UTF-8 BOM if present in response payload.
|
||||
- Added telemetry.
|
||||
|
||||
## v7.2.3
|
||||
- Fixing bug in calls to `DelayForBackoff` that caused doubling of delay
|
||||
duration.
|
||||
|
||||
## v7.2.2
|
||||
- autorest/azure: added ASM and ARM VM DNS suffixes.
|
||||
|
||||
## v7.2.1
|
||||
- fixed parsing of UTC times that are not RFC3339 conformant.
|
||||
|
||||
## v7.2.0
|
||||
- autorest/validation: Reformat validation error for better error message.
|
||||
|
||||
## v7.1.0
|
||||
- preparer: Added support for multipart formdata - WithMultiPartFormdata()
|
||||
- preparer: Added support for sending file in request body - WithFile
|
||||
- client: Added RetryDuration parameter.
|
||||
- autorest/validation: new package for validation code for Azure Go SDK.
|
||||
|
||||
## v7.0.7
|
||||
- Add trailing / to endpoint
|
||||
- azure: add EnvironmentFromName
|
||||
|
||||
## v7.0.6
|
||||
- Add retry logic for 408, 500, 502, 503 and 504 status codes.
|
||||
- Change url path and query encoding logic.
|
||||
- Fix DelayForBackoff for proper exponential delay.
|
||||
- Add CookieJar in Client.
|
||||
|
||||
## v7.0.5
|
||||
- Add check to start polling only when status is in [200,201,202].
|
||||
- Refactoring for unchecked errors.
|
||||
- azure/persist changes.
|
||||
- Fix 'file in use' issue in renewing token in deviceflow.
|
||||
- Store header RetryAfter for subsequent requests in polling.
|
||||
- Add attribute details in service error.
|
||||
|
||||
## v7.0.4
|
||||
- Better error messages for long running operation failures
|
||||
|
||||
## v7.0.3
|
||||
- Corrected DoPollForAsynchronous to properly handle the initial response
|
||||
|
||||
## v7.0.2
|
||||
- Corrected DoPollForAsynchronous to continue using the polling method first discovered
|
||||
|
||||
## v7.0.1
|
||||
- Fixed empty JSON input error in ByUnmarshallingJSON
|
||||
- Fixed polling support for GET calls
|
||||
- Changed format name from TimeRfc1123 to TimeRFC1123
|
||||
|
||||
## v7.0.0
|
||||
- Added ByCopying responder with supporting TeeReadCloser
|
||||
- Rewrote Azure asynchronous handling
|
||||
- Reverted to only unmarshalling JSON
|
||||
- Corrected handling of RFC3339 time strings and added support for Rfc1123 time format
|
||||
|
||||
The `json.Decoder` does not catch bad data as thoroughly as `json.Unmarshal`. Since
|
||||
`encoding/json` successfully deserializes all core types, and extended types normally provide
|
||||
their custom JSON serialization handlers, the code has been reverted back to using
|
||||
`json.Unmarshal`. The original change to use `json.Decode` was made to reduce duplicate
|
||||
code; there is no loss of function, and there is a gain in accuracy, by reverting.
|
||||
|
||||
Additionally, Azure services indicate requests to be polled by multiple means. The existing code
|
||||
only checked for one of those (that is, the presence of the `Azure-AsyncOperation` header).
|
||||
The new code correctly covers all cases and aligns with the other Azure SDKs.
|
||||
|
||||
## v6.1.0
|
||||
- Introduced `date.ByUnmarshallingJSONDate` and `date.ByUnmarshallingJSONTime` to enable JSON encoded values.
|
||||
|
||||
## v6.0.0
|
||||
- Completely reworked the handling of polled and asynchronous requests
|
||||
- Removed unnecessary routines
|
||||
- Reworked `mocks.Sender` to replay a series of `http.Response` objects
|
||||
- Added `PrepareDecorators` for primitive types (e.g., bool, int32)
|
||||
|
||||
Handling polled and asynchronous requests is no longer part of `Client#Send`. Instead new
|
||||
`SendDecorators` implement different styles of polled behavior. See`autorest.DoPollForStatusCodes`
|
||||
and `azure.DoPollForAsynchronous` for examples.
|
||||
|
||||
## v5.0.0
|
||||
- Added new RespondDecorators unmarshalling primitive types
|
||||
- Corrected application of inspection and authorization PrependDecorators
|
||||
|
||||
## v4.0.0
|
||||
- Added support for Azure long-running operations.
|
||||
- Added cancelation support to all decorators and functions that may delay.
|
||||
- Breaking: `DelayForBackoff` now accepts a channel, which may be nil.
|
||||
|
||||
## v3.1.0
|
||||
- Add support for OAuth Device Flow authorization.
|
||||
- Add support for ServicePrincipalTokens that are backed by an existing token, rather than other secret material.
|
||||
- Add helpers for persisting and restoring Tokens.
|
||||
- Increased code coverage in the github.com/Azure/autorest/azure package
|
||||
|
||||
## v3.0.0
|
||||
- Breaking: `NewErrorWithError` no longer takes `statusCode int`.
|
||||
- Breaking: `NewErrorWithStatusCode` is replaced with `NewErrorWithResponse`.
|
||||
- Breaking: `Client#Send()` no longer takes `codes ...int` argument.
|
||||
- Add: XML unmarshaling support with `ByUnmarshallingXML()`
|
||||
- Stopped vending dependencies locally and switched to [Glide](https://github.com/Masterminds/glide).
|
||||
Applications using this library should either use Glide or vendor dependencies locally some other way.
|
||||
- Add: `azure.WithErrorUnlessStatusCode()` decorator to handle Azure errors.
|
||||
- Fix: use `net/http.DefaultClient` as base client.
|
||||
- Fix: Missing inspection for polling responses added.
|
||||
- Add: CopyAndDecode helpers.
|
||||
- Improved `./autorest/to` with `[]string` helpers.
|
||||
- Removed golint suppressions in .travis.yml.
|
||||
|
||||
## v2.1.0
|
||||
|
||||
- Added `StatusCode` to `Error` for more easily obtaining the HTTP Reponse StatusCode (if any)
|
||||
|
||||
## v2.0.0
|
||||
|
||||
- Changed `to.StringMapPtr` method signature to return a pointer
|
||||
- Changed `ServicePrincipalCertificateSecret` and `NewServicePrincipalTokenFromCertificate` to support generic certificate and private keys
|
||||
|
||||
## v1.0.0
|
||||
|
||||
- Added Logging inspectors to trace http.Request / Response
|
||||
- Added support for User-Agent header
|
||||
- Changed WithHeader PrepareDecorator to use set vs. add
|
||||
- Added JSON to error when unmarshalling fails
|
||||
- Added Client#Send method
|
||||
- Corrected case of "Azure" in package paths
|
||||
- Added "to" helpers, Azure helpers, and improved ease-of-use
|
||||
- Corrected golint issues
|
||||
|
||||
## v1.0.1
|
||||
|
||||
- Added CHANGELOG.md
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Added mechanism to retrieve a ServicePrincipalToken using a certificate-signed JWT
|
||||
- Added an example of creating a certificate-based ServicePrincipal and retrieving an OAuth token using the certificate
|
||||
|
||||
## v1.1.1
|
||||
|
||||
- Introduce godeps and vendor dependencies introduced in v1.1.1
|
|
@ -1,23 +0,0 @@
|
|||
DIR?=./autorest/
|
||||
|
||||
default: build
|
||||
|
||||
build: fmt
|
||||
go install $(DIR)
|
||||
|
||||
test:
|
||||
go test $(DIR) || exit 1
|
||||
|
||||
vet:
|
||||
@echo "go vet ."
|
||||
@go vet $(DIR)... ; if [ $$? -eq 1 ]; then \
|
||||
echo ""; \
|
||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||
echo "and fix them if necessary before submitting the code for review."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
fmt:
|
||||
gofmt -w $(DIR)
|
||||
|
||||
.PHONY: build test vet fmt
|
|
@ -1,77 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = ""
|
||||
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6"
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7f175a633086a933d1940a7e7dc2154a0070a7c25fb4a2f671f3eef1a34d1fd7"
|
||||
name = "github.com/dimchansky/utfbom"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "5448fe645cb1964ba70ac8f9f2ffe975e61a536c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:096a8a9182648da3d00ff243b88407838902b6703fc12657f76890e08d1899bf"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = ""
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
"require",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:793a79198b755828dec284c6f1325e24e09186f1b7ba818b65c7c35104ed86eb"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"pkcs12",
|
||||
"pkcs12/internal/rc2",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "614d502a4dac94afa3a6ce146bd1736da82514c6"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/dgrijalva/jwt-go",
|
||||
"github.com/dimchansky/utfbom",
|
||||
"github.com/mitchellh/go-homedir",
|
||||
"github.com/stretchr/testify/require",
|
||||
"golang.org/x/crypto/pkcs12",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -1,41 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
version = "3.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/dimchansky/utfbom"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
|
@ -1,149 +0,0 @@
|
|||
# go-autorest
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/Azure/go-autorest/autorest?status.png)](https://godoc.org/github.com/Azure/go-autorest/autorest)
|
||||
[![Build Status](https://travis-ci.org/Azure/go-autorest.svg?branch=master)](https://travis-ci.org/Azure/go-autorest)
|
||||
[![Go Report Card](https://goreportcard.com/badge/Azure/go-autorest)](https://goreportcard.com/report/Azure/go-autorest)
|
||||
|
||||
Package go-autorest provides an HTTP request client for use with [Autorest](https://github.com/Azure/autorest.go)-generated API client packages.
|
||||
|
||||
An authentication client tested with Azure Active Directory (AAD) is also
|
||||
provided in this repo in the package
|
||||
`github.com/Azure/go-autorest/autorest/adal`. Despite its name, this package
|
||||
is maintained only as part of the Azure Go SDK and is not related to other
|
||||
"ADAL" libraries in [github.com/AzureAD](https://github.com/AzureAD).
|
||||
|
||||
## Overview
|
||||
|
||||
Package go-autorest implements an HTTP request pipeline suitable for use across
|
||||
multiple goroutines and provides the shared routines used by packages generated
|
||||
by [Autorest](https://github.com/Azure/autorest.go).
|
||||
|
||||
The package breaks sending and responding to HTTP requests into three phases: Preparing, Sending,
|
||||
and Responding. A typical pattern is:
|
||||
|
||||
```go
|
||||
req, err := Prepare(&http.Request{},
|
||||
token.WithAuthorization())
|
||||
|
||||
resp, err := Send(req,
|
||||
WithLogging(logger),
|
||||
DoErrorIfStatusCode(http.StatusInternalServerError),
|
||||
DoCloseIfError(),
|
||||
DoRetryForAttempts(5, time.Second))
|
||||
|
||||
err = Respond(resp,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
```
|
||||
|
||||
Each phase relies on decorators to modify and / or manage processing. Decorators may first modify
|
||||
and then pass the data along, pass the data first and then modify the result, or wrap themselves
|
||||
around passing the data (such as a logger might do). Decorators run in the order provided. For
|
||||
example, the following:
|
||||
|
||||
```go
|
||||
req, err := Prepare(&http.Request{},
|
||||
WithBaseURL("https://microsoft.com/"),
|
||||
WithPath("a"),
|
||||
WithPath("b"),
|
||||
WithPath("c"))
|
||||
```
|
||||
|
||||
will set the URL to:
|
||||
|
||||
```
|
||||
https://microsoft.com/a/b/c
|
||||
```
|
||||
|
||||
Preparers and Responders may be shared and re-used (assuming the underlying decorators support
|
||||
sharing and re-use). Performant use is obtained by creating one or more Preparers and Responders
|
||||
shared among multiple go-routines, and a single Sender shared among multiple sending go-routines,
|
||||
all bound together by means of input / output channels.
|
||||
|
||||
Decorators hold their passed state within a closure (such as the path components in the example
|
||||
above). Be careful to share Preparers and Responders only in a context where such held state
|
||||
applies. For example, it may not make sense to share a Preparer that applies a query string from a
|
||||
fixed set of values. Similarly, sharing a Responder that reads the response body into a passed
|
||||
struct (e.g., `ByUnmarshallingJson`) is likely incorrect.
|
||||
|
||||
Errors raised by autorest objects and methods will conform to the `autorest.Error` interface.
|
||||
|
||||
See the included examples for more detail. For details on the suggested use of this package by
|
||||
generated clients, see the Client described below.
|
||||
|
||||
## Helpers
|
||||
|
||||
### Handling Swagger Dates
|
||||
|
||||
The Swagger specification (https://swagger.io) that drives AutoRest
|
||||
(https://github.com/Azure/autorest/) precisely defines two date forms: date and date-time. The
|
||||
github.com/Azure/go-autorest/autorest/date package provides time.Time derivations to ensure correct
|
||||
parsing and formatting.
|
||||
|
||||
### Handling Empty Values
|
||||
|
||||
In JSON, missing values have different semantics than empty values. This is especially true for
|
||||
services using the HTTP PATCH verb. The JSON submitted with a PATCH request generally contains
|
||||
only those values to modify. Missing values are to be left unchanged. Developers, then, require a
|
||||
means to both specify an empty value and to leave the value out of the submitted JSON.
|
||||
|
||||
The Go JSON package (`encoding/json`) supports the `omitempty` tag. When specified, it omits
|
||||
empty values from the rendered JSON. Since Go defines default values for all base types (such as ""
|
||||
for string and 0 for int) and provides no means to mark a value as actually empty, the JSON package
|
||||
treats default values as meaning empty, omitting them from the rendered JSON. This means that, using
|
||||
the Go base types encoded through the default JSON package, it is not possible to create JSON to
|
||||
clear a value at the server.
|
||||
|
||||
The workaround within the Go community is to use pointers to base types in lieu of base types within
|
||||
structures that map to JSON. For example, instead of a value of type `string`, the workaround uses
|
||||
`*string`. While this enables distinguishing empty values from those to be unchanged, creating
|
||||
pointers to a base type (notably constant, in-line values) requires additional variables. This, for
|
||||
example,
|
||||
|
||||
```go
|
||||
s := struct {
|
||||
S *string
|
||||
}{ S: &"foo" }
|
||||
```
|
||||
fails, while, this
|
||||
|
||||
```go
|
||||
v := "foo"
|
||||
s := struct {
|
||||
S *string
|
||||
}{ S: &v }
|
||||
```
|
||||
succeeds.
|
||||
|
||||
To ease using pointers, the subpackage `to` contains helpers that convert to and from pointers for
|
||||
Go base types which have Swagger analogs. It also provides a helper that converts between
|
||||
`map[string]string` and `map[string]*string`, enabling the JSON to specify that the value
|
||||
associated with a key should be cleared. With the helpers, the previous example becomes
|
||||
|
||||
```go
|
||||
s := struct {
|
||||
S *string
|
||||
}{ S: to.StringPtr("foo") }
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/Azure/go-autorest/autorest
|
||||
go get github.com/Azure/go-autorest/autorest/azure
|
||||
go get github.com/Azure/go-autorest/autorest/date
|
||||
go get github.com/Azure/go-autorest/autorest/to
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See LICENSE file.
|
||||
|
||||
-----
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of
|
||||
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||
see the [Code of Conduct
|
||||
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||
questions or comments.
|
|
@ -1,298 +0,0 @@
|
|||
package main
|
||||
|
||||
// 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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/user"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
const (
|
||||
deviceMode = "device"
|
||||
clientSecretMode = "secret"
|
||||
clientCertMode = "cert"
|
||||
refreshMode = "refresh"
|
||||
|
||||
activeDirectoryEndpoint = "https://login.microsoftonline.com/"
|
||||
)
|
||||
|
||||
type option struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
var (
|
||||
mode string
|
||||
resource string
|
||||
|
||||
tenantID string
|
||||
applicationID string
|
||||
|
||||
applicationSecret string
|
||||
certificatePath string
|
||||
|
||||
tokenCachePath string
|
||||
)
|
||||
|
||||
func checkMandatoryOptions(mode string, options ...option) {
|
||||
for _, option := range options {
|
||||
if strings.TrimSpace(option.value) == "" {
|
||||
log.Fatalf("Authentication mode '%s' requires mandatory option '%s'.", mode, option.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultTokenCachePath() string {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defaultTokenPath := usr.HomeDir + "/.adal/accessToken.json"
|
||||
return defaultTokenPath
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&mode, "mode", "device", "authentication mode (device, secret, cert, refresh)")
|
||||
flag.StringVar(&resource, "resource", "", "resource for which the token is requested")
|
||||
flag.StringVar(&tenantID, "tenantId", "", "tenant id")
|
||||
flag.StringVar(&applicationID, "applicationId", "", "application id")
|
||||
flag.StringVar(&applicationSecret, "secret", "", "application secret")
|
||||
flag.StringVar(&certificatePath, "certificatePath", "", "path to pk12/PFC application certificate")
|
||||
flag.StringVar(&tokenCachePath, "tokenCachePath", defaultTokenCachePath(), "location of oath token cache")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
switch mode = strings.TrimSpace(mode); mode {
|
||||
case clientSecretMode:
|
||||
checkMandatoryOptions(clientSecretMode,
|
||||
option{name: "resource", value: resource},
|
||||
option{name: "tenantId", value: tenantID},
|
||||
option{name: "applicationId", value: applicationID},
|
||||
option{name: "secret", value: applicationSecret},
|
||||
)
|
||||
case clientCertMode:
|
||||
checkMandatoryOptions(clientCertMode,
|
||||
option{name: "resource", value: resource},
|
||||
option{name: "tenantId", value: tenantID},
|
||||
option{name: "applicationId", value: applicationID},
|
||||
option{name: "certificatePath", value: certificatePath},
|
||||
)
|
||||
case deviceMode:
|
||||
checkMandatoryOptions(deviceMode,
|
||||
option{name: "resource", value: resource},
|
||||
option{name: "tenantId", value: tenantID},
|
||||
option{name: "applicationId", value: applicationID},
|
||||
)
|
||||
case refreshMode:
|
||||
checkMandatoryOptions(refreshMode,
|
||||
option{name: "resource", value: resource},
|
||||
option{name: "tenantId", value: tenantID},
|
||||
option{name: "applicationId", value: applicationID},
|
||||
)
|
||||
default:
|
||||
log.Fatalln("Authentication modes 'secret, 'cert', 'device' or 'refresh' are supported.")
|
||||
}
|
||||
}
|
||||
|
||||
func acquireTokenClientSecretFlow(oauthConfig adal.OAuthConfig,
|
||||
appliationID string,
|
||||
applicationSecret string,
|
||||
resource string,
|
||||
callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(
|
||||
oauthConfig,
|
||||
appliationID,
|
||||
applicationSecret,
|
||||
resource,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return spt, spt.Refresh()
|
||||
}
|
||||
|
||||
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
|
||||
if !isRsaKey {
|
||||
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
|
||||
}
|
||||
|
||||
return certificate, rsaPrivateKey, nil
|
||||
}
|
||||
|
||||
func acquireTokenClientCertFlow(oauthConfig adal.OAuthConfig,
|
||||
applicationID string,
|
||||
applicationCertPath string,
|
||||
resource string,
|
||||
callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
|
||||
certData, err := ioutil.ReadFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
|
||||
}
|
||||
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromCertificate(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
certificate,
|
||||
rsaPrivateKey,
|
||||
resource,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return spt, spt.Refresh()
|
||||
}
|
||||
|
||||
func acquireTokenDeviceCodeFlow(oauthConfig adal.OAuthConfig,
|
||||
applicationID string,
|
||||
resource string,
|
||||
callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
|
||||
oauthClient := &http.Client{}
|
||||
deviceCode, err := adal.InitiateDeviceAuth(
|
||||
oauthClient,
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println(*deviceCode.Message)
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
return spt, err
|
||||
}
|
||||
|
||||
func refreshToken(oauthConfig adal.OAuthConfig,
|
||||
applicationID string,
|
||||
resource string,
|
||||
tokenCachePath string,
|
||||
callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
|
||||
token, err := adal.LoadToken(tokenCachePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load token from cache: %v", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spt, spt.Refresh()
|
||||
}
|
||||
|
||||
func saveToken(spt adal.Token) error {
|
||||
if tokenCachePath != "" {
|
||||
err := adal.SaveToken(tokenCachePath, 0600, spt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Acquired token was saved in '%s' file\n", tokenCachePath)
|
||||
return nil
|
||||
|
||||
}
|
||||
return fmt.Errorf("empty path for token cache")
|
||||
}
|
||||
|
||||
func main() {
|
||||
oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
callback := func(token adal.Token) error {
|
||||
return saveToken(token)
|
||||
}
|
||||
|
||||
log.Printf("Authenticating with mode '%s'\n", mode)
|
||||
switch mode {
|
||||
case clientSecretMode:
|
||||
_, err = acquireTokenClientSecretFlow(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
applicationSecret,
|
||||
resource,
|
||||
callback)
|
||||
case clientCertMode:
|
||||
_, err = acquireTokenClientCertFlow(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
certificatePath,
|
||||
resource,
|
||||
callback)
|
||||
case deviceMode:
|
||||
var spt *adal.ServicePrincipalToken
|
||||
spt, err = acquireTokenDeviceCodeFlow(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
callback)
|
||||
if err == nil {
|
||||
err = saveToken(spt.Token())
|
||||
}
|
||||
case refreshMode:
|
||||
_, err = refreshToken(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
tokenCachePath,
|
||||
callback)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to acquire a token for resource %s. Error: %v", resource, err)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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!")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,923 +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 (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"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.inner.AutoRefresh {
|
||||
t.Fatal("adal: ServicePrincipalToken did not default to automatic token refreshing")
|
||||
}
|
||||
|
||||
spt.SetAutoRefresh(false)
|
||||
if spt.inner.AutoRefresh {
|
||||
t.Fatal("adal: ServicePrincipalToken#SetAutoRefresh did not disable automatic token refreshing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicePrincipalTokenSetRefreshWithin(t *testing.T) {
|
||||
spt := newServicePrincipalToken()
|
||||
|
||||
if spt.inner.RefreshWithin != defaultRefresh {
|
||||
t.Fatal("adal: ServicePrincipalToken did not correctly set the default refresh interval")
|
||||
}
|
||||
|
||||
spt.SetRefreshWithin(2 * defaultRefresh)
|
||||
if spt.inner.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 TestServicePrincipalTokenFromMSIRefreshUsesGET(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 != "GET" {
|
||||
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set HTTP method -- expected %v, received %v", "GET", 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 TestServicePrincipalTokenFromMSIRefreshCancel(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
endpoint, _ := GetMSIVMEndpoint()
|
||||
|
||||
spt, err := NewServicePrincipalTokenFromMSI(endpoint, "https://resource")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get MSI SPT: %v", err)
|
||||
}
|
||||
|
||||
c := mocks.NewSender()
|
||||
c.AppendAndRepeatResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError), 5)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
start := time.Now()
|
||||
end := time.Now()
|
||||
|
||||
go func() {
|
||||
spt.SetSender(c)
|
||||
err = spt.RefreshWithContext(ctx)
|
||||
end = time.Now()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
cancel()
|
||||
wg.Wait()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
if end.Sub(start) >= time.Second {
|
||||
t.Fatalf("TestServicePrincipalTokenFromMSIRefreshCancel failed to cancel")
|
||||
}
|
||||
}
|
||||
|
||||
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 TestServicePrincipalTokenUsernamePasswordRefreshSetsBody(t *testing.T) {
|
||||
spt := newServicePrincipalTokenUsernamePassword(t)
|
||||
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
|
||||
body := string(b)
|
||||
|
||||
values, _ := url.ParseQuery(body)
|
||||
if values["client_id"][0] != "id" ||
|
||||
values["grant_type"][0] != "password" ||
|
||||
values["username"][0] != "username" ||
|
||||
values["password"][0] != "password" ||
|
||||
values["resource"][0] != "resource" {
|
||||
t.Fatalf("adal: ServicePrincipalTokenUsernamePassword#Refresh did not correctly set the HTTP Request Body.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServicePrincipalTokenAuthorizationCodeRefreshSetsBody(t *testing.T) {
|
||||
spt := newServicePrincipalTokenAuthorizationCode(t)
|
||||
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
|
||||
body := string(b)
|
||||
|
||||
values, _ := url.ParseQuery(body)
|
||||
if values["client_id"][0] != "id" ||
|
||||
values["grant_type"][0] != OAuthGrantTypeAuthorizationCode ||
|
||||
values["code"][0] != "code" ||
|
||||
values["client_secret"][0] != "clientSecret" ||
|
||||
values["redirect_uri"][0] != "http://redirectUri/getToken" ||
|
||||
values["resource"][0] != "resource" {
|
||||
t.Fatalf("adal: ServicePrincipalTokenAuthorizationCode#Refresh did not correctly set the HTTP Request Body.")
|
||||
}
|
||||
})
|
||||
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
|
||||
body := string(b)
|
||||
|
||||
values, _ := url.ParseQuery(body)
|
||||
if values["client_id"][0] != "id" ||
|
||||
values["grant_type"][0] != OAuthGrantTypeRefreshToken ||
|
||||
values["code"][0] != "code" ||
|
||||
values["client_secret"][0] != "clientSecret" ||
|
||||
values["redirect_uri"][0] != "http://redirectUri/getToken" ||
|
||||
values["resource"][0] != "resource" {
|
||||
t.Fatalf("adal: ServicePrincipalTokenAuthorizationCode#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.inner.Token.AccessToken != "accessToken" ||
|
||||
spt.inner.Token.ExpiresIn != "3600" ||
|
||||
spt.inner.Token.ExpiresOn != expiresOn ||
|
||||
spt.inner.Token.NotBefore != expiresOn ||
|
||||
spt.inner.Token.Resource != "resource" ||
|
||||
spt.inner.Token.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.inner.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 TestServicePrincipalTokenEnsureFreshFails(t *testing.T) {
|
||||
spt := newServicePrincipalToken()
|
||||
expireToken(&spt.inner.Token)
|
||||
|
||||
c := mocks.NewSender()
|
||||
c.SetError(fmt.Errorf("some failure"))
|
||||
|
||||
spt.SetSender(c)
|
||||
err := spt.EnsureFresh()
|
||||
if err == nil {
|
||||
t.Fatal("adal: ServicePrincipalToken#EnsureFresh didn't return an error")
|
||||
}
|
||||
if _, ok := err.(TokenRefreshError); !ok {
|
||||
t.Fatal("adal: ServicePrincipalToken#EnsureFresh didn't return a TokenRefreshError")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicePrincipalTokenEnsureFreshSkipsIfFresh(t *testing.T) {
|
||||
spt := newServicePrincipalToken()
|
||||
setTokenToExpireIn(&spt.inner.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.inner.Token.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.inner.Secret.(*ServicePrincipalMSISecret); !ok {
|
||||
t.Fatal("SPT secret was not of MSI type")
|
||||
}
|
||||
|
||||
if spt.inner.Resource != resource {
|
||||
t.Fatal("SPT came back with incorrect resource")
|
||||
}
|
||||
|
||||
if len(spt.refreshCallbacks) != 1 {
|
||||
t.Fatal("SPT had incorrect refresh callbacks.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) {
|
||||
resource := "https://resource"
|
||||
userID := "abc123"
|
||||
cb := func(token Token) error { return nil }
|
||||
|
||||
spt, err := NewServicePrincipalTokenFromMSIWithUserAssignedID("http://msiendpoint/", resource, userID, cb)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get MSI SPT: %v", err)
|
||||
}
|
||||
|
||||
// check some of the SPT fields
|
||||
if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); !ok {
|
||||
t.Fatal("SPT secret was not of MSI type")
|
||||
}
|
||||
|
||||
if spt.inner.Resource != resource {
|
||||
t.Fatal("SPT came back with incorrect resource")
|
||||
}
|
||||
|
||||
if len(spt.refreshCallbacks) != 1 {
|
||||
t.Fatal("SPT had incorrect refresh callbacks.")
|
||||
}
|
||||
|
||||
if spt.inner.ClientID != userID {
|
||||
t.Fatal("SPT had incorrect client ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServicePrincipalTokenFromManualTokenSecret(t *testing.T) {
|
||||
token := newToken()
|
||||
secret := &ServicePrincipalAuthorizationCodeSecret{
|
||||
ClientSecret: "clientSecret",
|
||||
AuthorizationCode: "code123",
|
||||
RedirectURI: "redirect",
|
||||
}
|
||||
|
||||
spt, err := NewServicePrincipalTokenFromManualTokenSecret(TestOAuthConfig, "id", "resource", *token, secret, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating new SPT: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*token, spt.inner.Token) {
|
||||
t.Fatalf("Tokens do not match: %s, %s", *token, spt.inner.Token)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(secret, spt.inner.Secret) {
|
||||
t.Fatalf("Secrets do not match: %s, %s", secret, spt.inner.Secret)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetVMEndpoint(t *testing.T) {
|
||||
endpoint, err := GetMSIVMEndpoint()
|
||||
if err != nil {
|
||||
t.Fatal("Coudn't get VM endpoint")
|
||||
}
|
||||
|
||||
if endpoint != msiEndpoint {
|
||||
t.Fatal("Didn't get correct endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalServicePrincipalNoSecret(t *testing.T) {
|
||||
spt := newServicePrincipalTokenManual()
|
||||
b, err := json.Marshal(spt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal token: %+v", err)
|
||||
}
|
||||
var spt2 *ServicePrincipalToken
|
||||
err = json.Unmarshal(b, &spt2)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal token: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(spt, spt2) {
|
||||
t.Fatal("tokens don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalServicePrincipalTokenSecret(t *testing.T) {
|
||||
spt := newServicePrincipalToken()
|
||||
b, err := json.Marshal(spt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal token: %+v", err)
|
||||
}
|
||||
var spt2 *ServicePrincipalToken
|
||||
err = json.Unmarshal(b, &spt2)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal token: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(spt, spt2) {
|
||||
t.Fatal("tokens don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalServicePrincipalCertificateSecret(t *testing.T) {
|
||||
spt := newServicePrincipalTokenCertificate(t)
|
||||
b, err := json.Marshal(spt)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when marshalling certificate token")
|
||||
}
|
||||
var spt2 *ServicePrincipalToken
|
||||
err = json.Unmarshal(b, &spt2)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when unmarshalling certificate token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalServicePrincipalMSISecret(t *testing.T) {
|
||||
spt, err := newServicePrincipalTokenFromMSI("http://msiendpoint/", "https://resource", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get MSI SPT: %+v", err)
|
||||
}
|
||||
b, err := json.Marshal(spt)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when marshalling MSI token")
|
||||
}
|
||||
var spt2 *ServicePrincipalToken
|
||||
err = json.Unmarshal(b, &spt2)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when unmarshalling MSI token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalServicePrincipalUsernamePasswordSecret(t *testing.T) {
|
||||
spt := newServicePrincipalTokenUsernamePassword(t)
|
||||
b, err := json.Marshal(spt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal token: %+v", err)
|
||||
}
|
||||
var spt2 *ServicePrincipalToken
|
||||
err = json.Unmarshal(b, &spt2)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal token: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(spt, spt2) {
|
||||
t.Fatal("tokens don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalServicePrincipalAuthorizationCodeSecret(t *testing.T) {
|
||||
spt := newServicePrincipalTokenAuthorizationCode(t)
|
||||
b, err := json.Marshal(spt)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal token: %+v", err)
|
||||
}
|
||||
var spt2 *ServicePrincipalToken
|
||||
err = json.Unmarshal(b, &spt2)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal token: %+v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(spt, spt2) {
|
||||
t.Fatal("tokens don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalInnerToken(t *testing.T) {
|
||||
spt := newServicePrincipalTokenManual()
|
||||
tokenJSON, err := spt.MarshalTokenJSON()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal token: %+v", err)
|
||||
}
|
||||
|
||||
testToken := newToken()
|
||||
testToken.RefreshToken = "refreshtoken"
|
||||
|
||||
testTokenJSON, err := json.Marshal(testToken)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal test token: %+v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tokenJSON, testTokenJSON) {
|
||||
t.Fatalf("tokens don't match: %s, %s", tokenJSON, testTokenJSON)
|
||||
}
|
||||
|
||||
var t1 *Token
|
||||
err = json.Unmarshal(tokenJSON, &t1)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal token: %+v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(t1, testToken) {
|
||||
t.Fatalf("tokens don't match: %s, %s", t1, testToken)
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
"refresh_token": "ABC123"
|
||||
}`,
|
||||
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
|
||||
}
|
||||
|
||||
func newServicePrincipalTokenUsernamePassword(t *testing.T) *ServicePrincipalToken {
|
||||
spt, _ := NewServicePrincipalTokenFromUsernamePassword(TestOAuthConfig, "id", "username", "password", "resource")
|
||||
return spt
|
||||
}
|
||||
|
||||
func newServicePrincipalTokenAuthorizationCode(t *testing.T) *ServicePrincipalToken {
|
||||
spt, _ := NewServicePrincipalTokenFromAuthorizationCode(TestOAuthConfig, "id", "clientSecret", "code", "http://redirectUri/getToken", "resource")
|
||||
return spt
|
||||
}
|
|
@ -1,230 +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.OAuthToken()) {
|
||||
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.OAuthToken()) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyAuthorization(t *testing.T) {
|
||||
|
||||
headers := make(map[string]interface{})
|
||||
queryParameters := make(map[string]interface{})
|
||||
|
||||
dummyAuthHeader := "dummyAuthHeader"
|
||||
dummyAuthHeaderValue := "dummyAuthHeaderValue"
|
||||
|
||||
dummyAuthQueryParameter := "dummyAuthQueryParameter"
|
||||
dummyAuthQueryParameterValue := "dummyAuthQueryParameterValue"
|
||||
|
||||
headers[dummyAuthHeader] = dummyAuthHeaderValue
|
||||
queryParameters[dummyAuthQueryParameter] = dummyAuthQueryParameterValue
|
||||
|
||||
aka := NewAPIKeyAuthorizer(headers, queryParameters)
|
||||
|
||||
req, err := Prepare(mocks.NewRequest(), aka.WithAuthorization())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization returned an error (%v)", err)
|
||||
} else if req.Header.Get(http.CanonicalHeaderKey(dummyAuthHeader)) != dummyAuthHeaderValue {
|
||||
t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization failed to set %s header", dummyAuthHeader)
|
||||
|
||||
} else if req.URL.Query().Get(dummyAuthQueryParameter) != dummyAuthQueryParameterValue {
|
||||
t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization failed to set %s query parameter", dummyAuthQueryParameterValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCognitivesServicesAuthorization(t *testing.T) {
|
||||
subscriptionKey := "dummyKey"
|
||||
csa := NewCognitiveServicesAuthorizer(subscriptionKey)
|
||||
req, err := Prepare(mocks.NewRequest(), csa.WithAuthorization())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization returned an error (%v)", err)
|
||||
} else if req.Header.Get(http.CanonicalHeaderKey(bingAPISdkHeader)) != golangBingAPISdkHeaderValue {
|
||||
t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization failed to set %s header", bingAPISdkHeader)
|
||||
} else if req.Header.Get(http.CanonicalHeaderKey(apiKeyAuthorizerHeader)) != subscriptionKey {
|
||||
t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization failed to set %s header", apiKeyAuthorizerHeader)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,444 +0,0 @@
|
|||
package auth
|
||||
|
||||
// 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"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/dimchansky/utfbom"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
// NewAuthorizerFromEnvironment creates an Authorizer configured from environment variables in the order:
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
// 3. Username password
|
||||
// 4. MSI
|
||||
func NewAuthorizerFromEnvironment() (autorest.Authorizer, error) {
|
||||
settings, err := getAuthenticationSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if settings.resource == "" {
|
||||
settings.resource = settings.environment.ResourceManagerEndpoint
|
||||
}
|
||||
|
||||
return settings.getAuthorizer()
|
||||
}
|
||||
|
||||
// NewAuthorizerFromEnvironmentWithResource creates an Authorizer configured from environment variables in the order:
|
||||
// 1. Client credentials
|
||||
// 2. Client certificate
|
||||
// 3. Username password
|
||||
// 4. MSI
|
||||
func NewAuthorizerFromEnvironmentWithResource(resource string) (autorest.Authorizer, error) {
|
||||
settings, err := getAuthenticationSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings.resource = resource
|
||||
return settings.getAuthorizer()
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
tenantID string
|
||||
clientID string
|
||||
clientSecret string
|
||||
certificatePath string
|
||||
certificatePassword string
|
||||
username string
|
||||
password string
|
||||
envName string
|
||||
resource string
|
||||
environment azure.Environment
|
||||
}
|
||||
|
||||
func getAuthenticationSettings() (s settings, err error) {
|
||||
s = settings{
|
||||
tenantID: os.Getenv("AZURE_TENANT_ID"),
|
||||
clientID: os.Getenv("AZURE_CLIENT_ID"),
|
||||
clientSecret: os.Getenv("AZURE_CLIENT_SECRET"),
|
||||
certificatePath: os.Getenv("AZURE_CERTIFICATE_PATH"),
|
||||
certificatePassword: os.Getenv("AZURE_CERTIFICATE_PASSWORD"),
|
||||
username: os.Getenv("AZURE_USERNAME"),
|
||||
password: os.Getenv("AZURE_PASSWORD"),
|
||||
envName: os.Getenv("AZURE_ENVIRONMENT"),
|
||||
resource: os.Getenv("AZURE_AD_RESOURCE"),
|
||||
}
|
||||
|
||||
if s.envName == "" {
|
||||
s.environment = azure.PublicCloud
|
||||
} else {
|
||||
s.environment, err = azure.EnvironmentFromName(s.envName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (settings settings) getAuthorizer() (autorest.Authorizer, error) {
|
||||
//1.Client Credentials
|
||||
if settings.clientSecret != "" {
|
||||
config := NewClientCredentialsConfig(settings.clientID, settings.clientSecret, settings.tenantID)
|
||||
config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.resource
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
//2. Client Certificate
|
||||
if settings.certificatePath != "" {
|
||||
config := NewClientCertificateConfig(settings.certificatePath, settings.certificatePassword, settings.clientID, settings.tenantID)
|
||||
config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.resource
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
//3. Username Password
|
||||
if settings.username != "" && settings.password != "" {
|
||||
config := NewUsernamePasswordConfig(settings.username, settings.password, settings.clientID, settings.tenantID)
|
||||
config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint
|
||||
config.Resource = settings.resource
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
// 4. MSI
|
||||
config := NewMSIConfig()
|
||||
config.Resource = settings.resource
|
||||
config.ClientID = settings.clientID
|
||||
return config.Authorizer()
|
||||
}
|
||||
|
||||
// NewAuthorizerFromFile creates an Authorizer configured from a configuration file.
|
||||
func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) {
|
||||
fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
|
||||
if fileLocation == "" {
|
||||
return nil, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set")
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fileLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Auth file might be encoded
|
||||
decoded, err := decode(contents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := file{}
|
||||
err = json.Unmarshal(decoded, &file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource, err := getResourceForToken(file, baseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// File represents the authentication file
|
||||
type file struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"`
|
||||
GraphResourceID string `json:"activeDirectoryGraphResourceId,omitempty"`
|
||||
SQLManagementEndpoint string `json:"sqlManagementEndpointUrl,omitempty"`
|
||||
GalleryEndpoint string `json:"galleryEndpointUrl,omitempty"`
|
||||
ManagementEndpoint string `json:"managementEndpointUrl,omitempty"`
|
||||
}
|
||||
|
||||
func decode(b []byte) ([]byte, error) {
|
||||
reader, enc := utfbom.Skip(bytes.NewReader(b))
|
||||
|
||||
switch enc {
|
||||
case utfbom.UTF16LittleEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.LittleEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
case utfbom.UTF16BigEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.BigEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
}
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
|
||||
func getResourceForToken(f file, baseURI string) (string, error) {
|
||||
// Compare dafault base URI from the SDK to the endpoints from the public cloud
|
||||
// Base URI and token resource are the same string. This func finds the authentication
|
||||
// file field that matches the SDK base URI. The SDK defines the public cloud
|
||||
// endpoint as its default base URI
|
||||
if !strings.HasSuffix(baseURI, "/") {
|
||||
baseURI += "/"
|
||||
}
|
||||
switch baseURI {
|
||||
case azure.PublicCloud.ServiceManagementEndpoint:
|
||||
return f.ManagementEndpoint, nil
|
||||
case azure.PublicCloud.ResourceManagerEndpoint:
|
||||
return f.ResourceManagerEndpoint, nil
|
||||
case azure.PublicCloud.ActiveDirectoryEndpoint:
|
||||
return f.ActiveDirectoryEndpoint, nil
|
||||
case azure.PublicCloud.GalleryEndpoint:
|
||||
return f.GalleryEndpoint, nil
|
||||
case azure.PublicCloud.GraphEndpoint:
|
||||
return f.GraphResourceID, nil
|
||||
}
|
||||
return "", fmt.Errorf("auth: base URI not found in endpoints")
|
||||
}
|
||||
|
||||
// NewClientCredentialsConfig creates an AuthorizerConfig object configured to obtain an Authorizer through Client Credentials.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string) ClientCredentialsConfig {
|
||||
return ClientCredentialsConfig{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientCertificateConfig creates a ClientCertificateConfig object configured to obtain an Authorizer through client certificate.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewClientCertificateConfig(certificatePath string, certificatePassword string, clientID string, tenantID string) ClientCertificateConfig {
|
||||
return ClientCertificateConfig{
|
||||
CertificatePath: certificatePath,
|
||||
CertificatePassword: certificatePassword,
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUsernamePasswordConfig creates an UsernamePasswordConfig object configured to obtain an Authorizer through username and password.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewUsernamePasswordConfig(username string, password string, clientID string, tenantID string) UsernamePasswordConfig {
|
||||
return UsernamePasswordConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMSIConfig creates an MSIConfig object configured to obtain an Authorizer through MSI.
|
||||
func NewMSIConfig() MSIConfig {
|
||||
return MSIConfig{
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeviceFlowConfig creates a DeviceFlowConfig object configured to obtain an Authorizer through device flow.
|
||||
// Defaults to Public Cloud and Resource Manager Endpoint.
|
||||
func NewDeviceFlowConfig(clientID string, tenantID string) DeviceFlowConfig {
|
||||
return DeviceFlowConfig{
|
||||
ClientID: clientID,
|
||||
TenantID: tenantID,
|
||||
Resource: azure.PublicCloud.ResourceManagerEndpoint,
|
||||
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
//AuthorizerConfig provides an authorizer from the configuration provided.
|
||||
type AuthorizerConfig interface {
|
||||
Authorizer() (autorest.Authorizer, error)
|
||||
}
|
||||
|
||||
// ClientCredentialsConfig provides the options to get a bearer authorizer from client credentials.
|
||||
type ClientCredentialsConfig struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from client credentials.
|
||||
func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate.
|
||||
type ClientCertificateConfig struct {
|
||||
ClientID string
|
||||
CertificatePath string
|
||||
CertificatePassword string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets an authorizer object from client certificate.
|
||||
func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
|
||||
|
||||
certData, err := ioutil.ReadFile(ccc.CertificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", ccc.CertificatePath, err)
|
||||
}
|
||||
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, ccc.CertificatePassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// DeviceFlowConfig provides the options to get a bearer authorizer using device flow authentication.
|
||||
type DeviceFlowConfig struct {
|
||||
ClientID string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from device flow.
|
||||
func (dfc DeviceFlowConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
oauthClient := &autorest.Client{}
|
||||
oauthConfig, err := adal.NewOAuthConfig(dfc.AADEndpoint, dfc.TenantID)
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
log.Println(*deviceCode.Message)
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
|
||||
if !isRsaKey {
|
||||
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
|
||||
}
|
||||
|
||||
return certificate, rsaPrivateKey, nil
|
||||
}
|
||||
|
||||
// UsernamePasswordConfig provides the options to get a bearer authorizer from a username and a password.
|
||||
type UsernamePasswordConfig struct {
|
||||
ClientID string
|
||||
Username string
|
||||
Password string
|
||||
TenantID string
|
||||
AADEndpoint string
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from a username and a password.
|
||||
func (ups UsernamePasswordConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID)
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from username and password auth: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// MSIConfig provides the options to get a bearer authorizer through MSI.
|
||||
type MSIConfig struct {
|
||||
Resource string
|
||||
ClientID string
|
||||
}
|
||||
|
||||
// Authorizer gets the authorizer from MSI.
|
||||
func (mc MSIConfig) Authorizer() (autorest.Authorizer, error) {
|
||||
msiEndpoint, err := adal.GetMSIVMEndpoint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err)
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
|
@ -1,108 +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 auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
expectedFile = file{
|
||||
ClientID: "client-id-123",
|
||||
ClientSecret: "client-secret-456",
|
||||
SubscriptionID: "sub-id-789",
|
||||
TenantID: "tenant-id-123",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com",
|
||||
ResourceManagerEndpoint: "https://management.azure.com/",
|
||||
GraphResourceID: "https://graph.windows.net/",
|
||||
SQLManagementEndpoint: "https://management.core.windows.net:8443/",
|
||||
GalleryEndpoint: "https://gallery.azure.com/",
|
||||
ManagementEndpoint: "https://management.core.windows.net/",
|
||||
}
|
||||
)
|
||||
|
||||
func TestNewAuthorizerFromFile(t *testing.T) {
|
||||
os.Setenv("AZURE_AUTH_LOCATION", filepath.Join(getCredsPath(), "credsutf16le.json"))
|
||||
authorizer, err := NewAuthorizerFromFile("https://management.azure.com")
|
||||
if err != nil || authorizer == nil {
|
||||
t.Logf("NewAuthorizerFromFile failed, got error %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAuthorizerFromEnvironment(t *testing.T) {
|
||||
os.Setenv("AZURE_TENANT_ID", expectedFile.TenantID)
|
||||
os.Setenv("AZURE_CLIENT_ID", expectedFile.ClientID)
|
||||
os.Setenv("AZURE_CLIENT_SECRET", expectedFile.ClientSecret)
|
||||
authorizer, err := NewAuthorizerFromEnvironment()
|
||||
|
||||
if err != nil || authorizer == nil {
|
||||
t.Logf("NewAuthorizerFromFile failed, got error %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeAndUnmarshal(t *testing.T) {
|
||||
tests := []string{
|
||||
"credsutf8.json",
|
||||
"credsutf16le.json",
|
||||
"credsutf16be.json",
|
||||
}
|
||||
creds := getCredsPath()
|
||||
for _, test := range tests {
|
||||
b, err := ioutil.ReadFile(filepath.Join(creds, test))
|
||||
if err != nil {
|
||||
t.Logf("error reading file '%s': %s", test, err)
|
||||
t.Fail()
|
||||
}
|
||||
decoded, err := decode(b)
|
||||
if err != nil {
|
||||
t.Logf("error decoding file '%s': %s", test, err)
|
||||
t.Fail()
|
||||
}
|
||||
var got file
|
||||
err = json.Unmarshal(decoded, &got)
|
||||
if err != nil {
|
||||
t.Logf("error unmarshaling file '%s': %s", test, err)
|
||||
t.Fail()
|
||||
}
|
||||
if !reflect.DeepEqual(expectedFile, got) {
|
||||
t.Logf("unmarshaled map expected %v, got %v", expectedFile, got)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCredsPath() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
return filepath.Join(gopath, "src", "github.com", "Azure", "go-autorest", "testdata")
|
||||
}
|
||||
|
||||
func areMapsEqual(a, b map[string]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for k := range a {
|
||||
if a[k] != b[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -1,668 +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_FoundAzureFullError(t *testing.T) {
|
||||
j := `{
|
||||
"error": {
|
||||
"code": "InternalError",
|
||||
"message": "Azure is having trouble right now.",
|
||||
"target": "target1",
|
||||
"details": [{"code": "conflict1", "message":"error message1"},
|
||||
{"code": "conflict2", "message":"error message2"}],
|
||||
"innererror": { "customKey": "customValue" },
|
||||
"additionalInfo": [{"type": "someErrorType", "info": {"someProperty": "someValue"}}]
|
||||
}
|
||||
}`
|
||||
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")
|
||||
}
|
||||
|
||||
if *azErr.ServiceError.Target == "" {
|
||||
t.Fatalf("azure: error target is not unmarshaled properly")
|
||||
}
|
||||
|
||||
d, _ := json.Marshal(azErr.ServiceError.Details)
|
||||
if string(d) != `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]` {
|
||||
t.Fatalf("azure: error details is not unmarshaled properly")
|
||||
}
|
||||
|
||||
i, _ := json.Marshal(azErr.ServiceError.InnerError)
|
||||
if string(i) != `{"customKey":"customValue"}` {
|
||||
t.Fatalf("azure: inner error is not unmarshaled properly")
|
||||
}
|
||||
|
||||
a, _ := json.Marshal(azErr.ServiceError.AdditionalInfo)
|
||||
if string(a) != `[{"info":{"someProperty":"someValue"},"type":"someErrorType"}]` {
|
||||
t.Fatalf("azure: error additional info 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",
|
||||
Details: []map[string]interface{}{
|
||||
{"Status": "NotFound"},
|
||||
},
|
||||
}
|
||||
|
||||
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 := `{
|
||||
"code": "InternalError",
|
||||
"message": "Azure is having trouble right now.",
|
||||
"target": "target1",
|
||||
"details": [{"code": "conflict1", "message":"error message1"},
|
||||
{"code": "conflict2", "message":"error message2"}],
|
||||
"innererror": { "customKey": "customValue" },
|
||||
"additionalInfo": [{"type": "someErrorType", "info": {"someProperty": "someValue"}}]
|
||||
}`
|
||||
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.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()
|
||||
}
|
||||
|
||||
if azErr.ServiceError == nil {
|
||||
t.Logf("`ServiceError` was nil when it shouldn't have been.")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if expected := "target1"; *azErr.ServiceError.Target != expected {
|
||||
t.Logf("Incorrect Target\n\tgot: %q\n\twant: %q", *azErr.ServiceError.Target, expected)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
expectedServiceErrorDetails := `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]`
|
||||
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 details was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(details), expectedServiceErrorDetails)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
expectedServiceErrorInnerError := `{"customKey":"customValue"}`
|
||||
if azErr.ServiceError.InnerError == nil {
|
||||
t.Logf("`ServiceError.InnerError` was nil when it should have been %q", expectedServiceErrorInnerError)
|
||||
t.Fail()
|
||||
} else if innerError, _ := json.Marshal(azErr.ServiceError.InnerError); expectedServiceErrorInnerError != string(innerError) {
|
||||
t.Logf("Inner error was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(innerError), expectedServiceErrorInnerError)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
expectedServiceErrorAdditionalInfo := `[{"info":{"someProperty":"someValue"},"type":"someErrorType"}]`
|
||||
if azErr.ServiceError.AdditionalInfo == nil {
|
||||
t.Logf("`ServiceError.AdditionalInfo` was nil when it should have been %q", expectedServiceErrorAdditionalInfo)
|
||||
t.Fail()
|
||||
} else if additionalInfo, _ := json.Marshal(azErr.ServiceError.AdditionalInfo); expectedServiceErrorAdditionalInfo != string(additionalInfo) {
|
||||
t.Logf("Additional info was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(additionalInfo), expectedServiceErrorAdditionalInfo)
|
||||
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",
|
||||
"target": "target1",
|
||||
"details": [{"code": "conflict1", "message":"error message1"}],
|
||||
"innererror": { "customKey": "customValue" },
|
||||
"additionalInfo": [{"type": "someErrorType", "info": {"someProperty": "someValue"}}]
|
||||
}
|
||||
}`
|
||||
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\" Target=\"target1\" Details=[{\"code\":\"conflict1\",\"message\":\"error message1\"}] InnerError={\"customKey\":\"customValue\"} AdditionalInfo=[{\"info\":{\"someProperty\":\"someValue\"},\"type\":\"someErrorType\"}]"
|
||||
if expected != azErr.Error() {
|
||||
t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, azErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestErrorString_WithErrorNonConforming(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 TestParseResourceID_WithValidBasicResourceID(t *testing.T) {
|
||||
|
||||
basicResourceID := "/subscriptions/subid-3-3-4/resourceGroups/regGroupVladdb/providers/Microsoft.Network/LoadBalancer/testResourceName"
|
||||
want := Resource{
|
||||
SubscriptionID: "subid-3-3-4",
|
||||
ResourceGroup: "regGroupVladdb",
|
||||
Provider: "Microsoft.Network",
|
||||
ResourceType: "LoadBalancer",
|
||||
ResourceName: "testResourceName",
|
||||
}
|
||||
got, err := ParseResourceID(basicResourceID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("azure: error returned while parsing valid resourceId")
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Logf("got: %+v\nwant: %+v", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResourceID_WithValidSubResourceID(t *testing.T) {
|
||||
subresourceID := "/subscriptions/subid-3-3-4/resourceGroups/regGroupVladdb/providers/Microsoft.Network/LoadBalancer/resource/is/a/subresource/actualresourceName"
|
||||
want := Resource{
|
||||
SubscriptionID: "subid-3-3-4",
|
||||
ResourceGroup: "regGroupVladdb",
|
||||
Provider: "Microsoft.Network",
|
||||
ResourceType: "LoadBalancer",
|
||||
ResourceName: "actualresourceName",
|
||||
}
|
||||
got, err := ParseResourceID(subresourceID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("azure: error returned while parsing valid resourceId")
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Logf("got: %+v\nwant: %+v", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResourceID_WithIncompleteResourceID(t *testing.T) {
|
||||
basicResourceID := "/subscriptions/subid-3-3-4/resourceGroups/regGroupVladdb/providers/Microsoft.Network/"
|
||||
want := Resource{}
|
||||
|
||||
got, err := ParseResourceID(basicResourceID)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("azure: no error returned on incomplete resource id")
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Logf("got: %+v\nwant: %+v", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResourceID_WithMalformedResourceID(t *testing.T) {
|
||||
malformedResourceID := "/providers/subid-3-3-4/resourceGroups/regGroupVladdb/subscriptions/Microsoft.Network/LoadBalancer/testResourceName"
|
||||
want := Resource{}
|
||||
|
||||
got, err := ParseResourceID(malformedResourceID)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("azure: error returned while parsing malformed resourceID")
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Logf("got: %+v\nwant: %+v", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package cli
|
||||
|
||||
// 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"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Profile represents a Profile from the Azure CLI
|
||||
type Profile struct {
|
||||
InstallationID string `json:"installationId"`
|
||||
Subscriptions []Subscription `json:"subscriptions"`
|
||||
}
|
||||
|
||||
// Subscription represents a Subscription from the Azure CLI
|
||||
type Subscription struct {
|
||||
EnvironmentName string `json:"environmentName"`
|
||||
ID string `json:"id"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
TenantID string `json:"tenantId"`
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
// User represents a User from the Azure CLI
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ProfilePath returns the path where the Azure Profile is stored from the Azure CLI
|
||||
func ProfilePath() (string, error) {
|
||||
return homedir.Expand("~/.azure/azureProfile.json")
|
||||
}
|
||||
|
||||
// LoadProfile restores a Profile object from a file located at 'path'.
|
||||
func LoadProfile(path string) (result Profile, err error) {
|
||||
var contents []byte
|
||||
contents, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
|
||||
return
|
||||
}
|
||||
reader := utfbom.SkipOnly(bytes.NewReader(contents))
|
||||
|
||||
dec := json.NewDecoder(reader)
|
||||
if err = dec.Decode(&result); err != nil {
|
||||
err = fmt.Errorf("failed to decode contents of file (%s) into a Profile representation: %v", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package cli
|
||||
|
||||
// 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"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Token represents an AccessToken from the Azure CLI
|
||||
type Token struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
Authority string `json:"_authority"`
|
||||
ClientID string `json:"_clientId"`
|
||||
ExpiresOn string `json:"expiresOn"`
|
||||
IdentityProvider string `json:"identityProvider"`
|
||||
IsMRRT bool `json:"isMRRT"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
Resource string `json:"resource"`
|
||||
TokenType string `json:"tokenType"`
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
||||
// ToADALToken converts an Azure CLI `Token`` to an `adal.Token``
|
||||
func (t Token) ToADALToken() (converted adal.Token, err error) {
|
||||
tokenExpirationDate, err := ParseExpirationDate(t.ExpiresOn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error parsing Token Expiration Date %q: %+v", t.ExpiresOn, err)
|
||||
return
|
||||
}
|
||||
|
||||
difference := tokenExpirationDate.Sub(date.UnixEpoch())
|
||||
|
||||
converted = adal.Token{
|
||||
AccessToken: t.AccessToken,
|
||||
Type: t.TokenType,
|
||||
ExpiresIn: "3600",
|
||||
ExpiresOn: strconv.Itoa(int(difference.Seconds())),
|
||||
RefreshToken: t.RefreshToken,
|
||||
Resource: t.Resource,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AccessTokensPath returns the path where access tokens are stored from the Azure CLI
|
||||
// TODO(#199): add unit test.
|
||||
func AccessTokensPath() (string, error) {
|
||||
// Azure-CLI allows user to customize the path of access tokens thorugh environment variable.
|
||||
var accessTokenPath = os.Getenv("AZURE_ACCESS_TOKEN_FILE")
|
||||
var err error
|
||||
|
||||
// Fallback logic to default path on non-cloud-shell environment.
|
||||
// TODO(#200): remove the dependency on hard-coding path.
|
||||
if accessTokenPath == "" {
|
||||
accessTokenPath, err = homedir.Expand("~/.azure/accessTokens.json")
|
||||
}
|
||||
|
||||
return accessTokenPath, err
|
||||
}
|
||||
|
||||
// ParseExpirationDate parses either a Azure CLI or CloudShell date into a time object
|
||||
func ParseExpirationDate(input string) (*time.Time, error) {
|
||||
// CloudShell (and potentially the Azure CLI in future)
|
||||
expirationDate, cloudShellErr := time.Parse(time.RFC3339, input)
|
||||
if cloudShellErr != nil {
|
||||
// Azure CLI (Python) e.g. 2017-08-31 19:48:57.998857 (plus the local timezone)
|
||||
const cliFormat = "2006-01-02 15:04:05.999999"
|
||||
expirationDate, cliErr := time.ParseInLocation(cliFormat, input, time.Local)
|
||||
if cliErr == nil {
|
||||
return &expirationDate, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Error parsing expiration date %q.\n\nCloudShell Error: \n%+v\n\nCLI Error:\n%+v", input, cloudShellErr, cliErr)
|
||||
}
|
||||
|
||||
return &expirationDate, nil
|
||||
}
|
||||
|
||||
// LoadTokens restores a set of Token objects from a file located at 'path'.
|
||||
func LoadTokens(path string) ([]Token, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var tokens []Token
|
||||
|
||||
dec := json.NewDecoder(file)
|
||||
if err = dec.Decode(&tokens); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode contents of file (%s) into a `cli.Token` representation: %v", path, err)
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
|
@ -1,348 +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"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"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--",
|
||||
TokenAudience: "--token-audience",
|
||||
}
|
||||
|
||||
func TestEnvironment_EnvironmentFromURL_NoOverride_Success(t *testing.T) {
|
||||
fileContents, _ := ioutil.ReadFile(filepath.Join("testdata", "test_metadata_environment_1.json"))
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(fileContents))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
got, err := EnvironmentFromURL(ts.URL)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got.Name != "HybridEnvironment" {
|
||||
t.Logf("got: %v want: HybridEnvironment", got.Name)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_EnvironmentFromURL_OverrideStorageSuffix_Success(t *testing.T) {
|
||||
fileContents, _ := ioutil.ReadFile(filepath.Join("testdata", "test_metadata_environment_1.json"))
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(fileContents))
|
||||
}))
|
||||
defer ts.Close()
|
||||
overrideProperty := OverrideProperty{
|
||||
Key: EnvironmentStorageEndpointSuffix,
|
||||
Value: "fakeStorageSuffix",
|
||||
}
|
||||
got, err := EnvironmentFromURL(ts.URL, overrideProperty)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got.StorageEndpointSuffix != "fakeStorageSuffix" {
|
||||
t.Logf("got: %v want: fakeStorageSuffix", got.StorageEndpointSuffix)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_EnvironmentFromURL_EmptyEndpoint_Failure(t *testing.T) {
|
||||
_, err := EnvironmentFromURL("")
|
||||
|
||||
if err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
if err.Error() != "Metadata resource manager endpoint is empty" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
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--",
|
||||
"serviceBusEndpoint": "--service-bus-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 "--service-bus-endpoint--" != testSubject.ServiceBusEndpoint {
|
||||
t.Errorf("Expected ServiceBusEndpoint to be \"--service-bus-endpoint--\", but goet %q", testSubject.ServiceBusEndpoint)
|
||||
}
|
||||
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--",
|
||||
ServiceBusEndpoint: "--service-bus-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.ServiceBusEndpoint != testSubject.ServiceBusEndpoint {
|
||||
t.Errorf("Expected ServiceBusEnpoint to be %q, but got %q", env.ServiceBusEndpoint, testSubject.ServiceBusEndpoint)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
# autorest azure example
|
||||
|
||||
## Usage (device mode)
|
||||
|
||||
This shows how to use the example for device auth.
|
||||
|
||||
1. Execute this. It will save your token to /tmp/azure-example-token:
|
||||
|
||||
```
|
||||
./example -tenantId "13de0a15-b5db-44b9-b682-b4ba82afbd29" -subscriptionId "aff271ee-e9be-4441-b9bb-42f5af4cbaeb" -mode "device" -tokenCachePath "/tmp/azure-example-token"
|
||||
```
|
||||
|
||||
2. Execute it again, it will load the token from cache and not prompt for auth again.
|
||||
|
||||
## Usage (certificate mode)
|
||||
|
||||
This example covers how to make an authenticated call to the Azure Resource Manager APIs, using certificate-based authentication.
|
||||
|
||||
0. Export some required variables
|
||||
|
||||
```
|
||||
export SUBSCRIPTION_ID="aff271ee-e9be-4441-b9bb-42f5af4cbaeb"
|
||||
export TENANT_ID="13de0a15-b5db-44b9-b682-b4ba82afbd29"
|
||||
export RESOURCE_GROUP="someresourcegroup"
|
||||
```
|
||||
|
||||
* replace both values with your own
|
||||
|
||||
1. Create a private key
|
||||
|
||||
```
|
||||
openssl genrsa -out "example.key" 2048
|
||||
```
|
||||
|
||||
|
||||
|
||||
2. Create the certificate
|
||||
|
||||
```
|
||||
openssl req -new -key "example.key" -subj "/CN=example" -out "example.csr"
|
||||
|
||||
openssl x509 -req -in "example.csr" -signkey "example.key" -out "example.crt" -days 10000
|
||||
```
|
||||
|
||||
|
||||
|
||||
3. Create the PKCS12 version of the certificate (with no password)
|
||||
|
||||
```
|
||||
openssl pkcs12 -export -out "example.pfx" -inkey "example.key" -in "example.crt" -passout pass:
|
||||
```
|
||||
|
||||
|
||||
|
||||
4. Register a new Azure AD Application with the certificate contents
|
||||
|
||||
```
|
||||
certificateContents="$(tail -n+2 "example.key" | head -n-1)"
|
||||
|
||||
azure ad app create \
|
||||
--name "example-azuread-app" \
|
||||
--home-page="http://example-azuread-app/home" \
|
||||
--identifier-uris "http://example-azuread-app/app" \
|
||||
--key-usage "Verify" \
|
||||
--end-date "2020-01-01" \
|
||||
--key-value "${certificateContents}"
|
||||
```
|
||||
|
||||
|
||||
|
||||
5. Create a new service principal using the "Application Id" from the previous step
|
||||
|
||||
```
|
||||
azure ad sp create "APPLICATION_ID"
|
||||
```
|
||||
|
||||
* Replace APPLICATION_ID with the "Application Id" returned in step 4
|
||||
|
||||
|
||||
|
||||
6. Grant your service principal necessary permissions
|
||||
|
||||
```
|
||||
azure role assignment create \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--roleName "Contributor" \
|
||||
--subscription "${SUBSCRIPTION_ID}" \
|
||||
--spn "http://example-azuread-app/app"
|
||||
```
|
||||
|
||||
* Replace SUBSCRIPTION_ID with your subscription id
|
||||
* Replace RESOURCE_GROUP with the resource group for the assignment
|
||||
* Ensure that the `spn` parameter matches an `identifier-url` from Step 4
|
||||
|
||||
|
||||
|
||||
7. Run this example app to see your resource groups
|
||||
|
||||
```
|
||||
go run main.go \
|
||||
--tenantId="${TENANT_ID}" \
|
||||
--subscriptionId="${SUBSCRIPTION_ID}" \
|
||||
--applicationId="http://example-azuread-app/app" \
|
||||
--certificatePath="certificate.pfx"
|
||||
```
|
||||
|
||||
|
||||
You should see something like this as output:
|
||||
|
||||
```
|
||||
2015/11/08 18:28:39 Using these settings:
|
||||
2015/11/08 18:28:39 * certificatePath: certificate.pfx
|
||||
2015/11/08 18:28:39 * applicationID: http://example-azuread-app/app
|
||||
2015/11/08 18:28:39 * tenantID: 13de0a15-b5db-44b9-b682-b4ba82afbd29
|
||||
2015/11/08 18:28:39 * subscriptionID: aff271ee-e9be-4441-b9bb-42f5af4cbaeb
|
||||
2015/11/08 18:28:39 loading certificate...
|
||||
2015/11/08 18:28:39 retrieve oauth token...
|
||||
2015/11/08 18:28:39 querying the list of resource groups...
|
||||
2015/11/08 18:28:50
|
||||
2015/11/08 18:28:50 Groups: {"value":[{"id":"/subscriptions/aff271ee-e9be-4441-b9bb-42f5af4cbaeb/resourceGroups/kube-66f30810","name":"kube-66f30810","location":"westus","tags":{},"properties":{"provisioningState":"Succeeded"}}]}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
You may need to wait sometime between executing step 4, step 5 and step 6. If you issue those requests too quickly, you might hit an AD server that is not consistent with the server where the resource was created.
|
|
@ -1,272 +0,0 @@
|
|||
package main
|
||||
|
||||
// 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/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceGroupURLTemplate = "https://management.azure.com"
|
||||
apiVersion = "2015-01-01"
|
||||
nativeAppClientID = "a87032a7-203c-4bf7-913c-44c50d23409a"
|
||||
resource = "https://management.core.windows.net/"
|
||||
)
|
||||
|
||||
var (
|
||||
mode string
|
||||
tenantID string
|
||||
subscriptionID string
|
||||
applicationID string
|
||||
|
||||
tokenCachePath string
|
||||
forceRefresh bool
|
||||
impatient bool
|
||||
|
||||
certificatePath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&mode, "mode", "device", "mode of operation for SPT creation")
|
||||
flag.StringVar(&certificatePath, "certificatePath", "", "path to pk12/pfx certificate")
|
||||
flag.StringVar(&applicationID, "applicationId", "", "application id")
|
||||
flag.StringVar(&tenantID, "tenantId", "", "tenant id")
|
||||
flag.StringVar(&subscriptionID, "subscriptionId", "", "subscription id")
|
||||
flag.StringVar(&tokenCachePath, "tokenCachePath", "", "location of oauth token cache")
|
||||
flag.BoolVar(&forceRefresh, "forceRefresh", false, "pass true to force a token refresh")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("mode(%s) certPath(%s) appID(%s) tenantID(%s), subID(%s)\n",
|
||||
mode, certificatePath, applicationID, tenantID, subscriptionID)
|
||||
|
||||
if mode == "certificate" &&
|
||||
(strings.TrimSpace(tenantID) == "" || strings.TrimSpace(subscriptionID) == "") {
|
||||
log.Fatalln("Bad usage. Using certificate mode. Please specify tenantID, subscriptionID")
|
||||
}
|
||||
|
||||
if mode != "certificate" && mode != "device" {
|
||||
log.Fatalln("Bad usage. Mode must be one of 'certificate' or 'device'.")
|
||||
}
|
||||
|
||||
if mode == "device" && strings.TrimSpace(applicationID) == "" {
|
||||
log.Println("Using device mode auth. Will use `azkube` clientID since none was specified on the comand line.")
|
||||
applicationID = nativeAppClientID
|
||||
}
|
||||
|
||||
if mode == "certificate" && strings.TrimSpace(certificatePath) == "" {
|
||||
log.Fatalln("Bad usage. Mode 'certificate' requires the 'certificatePath' argument.")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(subscriptionID) == "" || strings.TrimSpace(applicationID) == "" {
|
||||
log.Fatalln("Bad usage. Must specify the 'tenantId' and 'subscriptionId'")
|
||||
}
|
||||
}
|
||||
|
||||
func getSptFromCachedToken(oauthConfig adal.OAuthConfig, clientID, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
token, err := adal.LoadToken(tokenCachePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load token from cache: %v", err)
|
||||
}
|
||||
|
||||
spt, _ := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
|
||||
if !isRsaKey {
|
||||
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
|
||||
}
|
||||
|
||||
return certificate, rsaPrivateKey, nil
|
||||
}
|
||||
|
||||
func getSptFromCertificate(oauthConfig adal.OAuthConfig, clientID, resource, certicatePath string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
certData, err := ioutil.ReadFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
|
||||
}
|
||||
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spt, _ := adal.NewServicePrincipalTokenFromCertificate(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
certificate,
|
||||
rsaPrivateKey,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
func getSptFromDeviceFlow(oauthConfig adal.OAuthConfig, clientID, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
oauthClient := &autorest.Client{}
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, oauthConfig, clientID, resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println(*deviceCode.Message)
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
|
||||
}
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
func printResourceGroups(client *autorest.Client) error {
|
||||
p := map[string]interface{}{"subscription-id": subscriptionID}
|
||||
q := map[string]interface{}{"api-version": apiVersion}
|
||||
|
||||
req, _ := autorest.Prepare(&http.Request{},
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(resourceGroupURLTemplate),
|
||||
autorest.WithPathParameters("/subscriptions/{subscription-id}/resourcegroups", p),
|
||||
autorest.WithQueryParameters(q))
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := struct {
|
||||
ResourceGroups []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"value"`
|
||||
}{}
|
||||
|
||||
defer resp.Body.Close()
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
err = dec.Decode(&value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var groupNames = make([]string, len(value.ResourceGroups))
|
||||
for i, name := range value.ResourceGroups {
|
||||
groupNames[i] = name.Name
|
||||
}
|
||||
|
||||
log.Println("Groups:", strings.Join(groupNames, ", "))
|
||||
return err
|
||||
}
|
||||
|
||||
func saveToken(spt adal.Token) {
|
||||
if tokenCachePath != "" {
|
||||
err := adal.SaveToken(tokenCachePath, 0600, spt)
|
||||
if err != nil {
|
||||
log.Println("error saving token", err)
|
||||
} else {
|
||||
log.Println("saved token to", tokenCachePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var spt *adal.ServicePrincipalToken
|
||||
var err error
|
||||
|
||||
callback := func(t adal.Token) error {
|
||||
log.Println("refresh callback was called")
|
||||
saveToken(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if tokenCachePath != "" {
|
||||
log.Println("tokenCachePath specified; attempting to load from", tokenCachePath)
|
||||
spt, err = getSptFromCachedToken(*oauthConfig, applicationID, resource, callback)
|
||||
if err != nil {
|
||||
spt = nil // just in case, this is the condition below
|
||||
log.Println("loading from cache failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if spt == nil {
|
||||
log.Println("authenticating via 'mode'", mode)
|
||||
switch mode {
|
||||
case "device":
|
||||
spt, err = getSptFromDeviceFlow(*oauthConfig, applicationID, resource, callback)
|
||||
case "certificate":
|
||||
spt, err = getSptFromCertificate(*oauthConfig, applicationID, resource, certificatePath, callback)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln("failed to retrieve token:", err)
|
||||
}
|
||||
|
||||
// should save it as soon as you get it since Refresh won't be called for some time
|
||||
if tokenCachePath != "" {
|
||||
saveToken(spt.Token())
|
||||
}
|
||||
}
|
||||
|
||||
client := &autorest.Client{}
|
||||
client.Authorizer = autorest.NewBearerAuthorizer(spt)
|
||||
|
||||
printResourceGroups(client)
|
||||
|
||||
if forceRefresh {
|
||||
err = spt.Refresh()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
printResourceGroups(client)
|
||||
}
|
||||
}
|
|
@ -1,169 +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 (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRetrySkipRegistration(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"))
|
||||
|
||||
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,
|
||||
SkipResourceProviderRegistration: true,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("got error: %v", err)
|
||||
}
|
||||
|
||||
autorest.Respond(r,
|
||||
autorest.ByDiscardingBody(),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
|
||||
if r.StatusCode != http.StatusConflict {
|
||||
t.Fatalf("azure: Sender#DoRetryWithRegistration -- Got: StatusCode %v; Want: StatusCode 409 Conflict", r.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRetryWithRegistration_CanBeCancelled(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
delay := 5 * time.Second
|
||||
|
||||
client := mocks.NewSender()
|
||||
client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError), 5)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
start := time.Now()
|
||||
end := time.Now()
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
req := mocks.NewRequestForURL("https://lol/subscriptions/rofl")
|
||||
req = req.WithContext(ctx)
|
||||
req.Body = mocks.NewBody("lolol")
|
||||
_, err = autorest.SendWithSender(client, req,
|
||||
DoRetryWithRegistration(autorest.Client{
|
||||
PollingDelay: time.Second,
|
||||
PollingDuration: delay,
|
||||
RetryAttempts: 5,
|
||||
RetryDuration: time.Second,
|
||||
Sender: client,
|
||||
SkipResourceProviderRegistration: true,
|
||||
}),
|
||||
)
|
||||
end = time.Now()
|
||||
wg.Done()
|
||||
}()
|
||||
cancel()
|
||||
wg.Wait()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
if err == nil {
|
||||
t.Fatalf("azure: DoRetryWithRegistration didn't cancel")
|
||||
}
|
||||
if end.Sub(start) >= delay {
|
||||
t.Fatalf("azure: DoRetryWithRegistration failed to cancel")
|
||||
}
|
||||
}
|
20
vendor/github.com/Azure/go-autorest/autorest/azure/testdata/test_environment_1.json
generated
vendored
20
vendor/github.com/Azure/go-autorest/autorest/azure/testdata/test_environment_1.json
generated
vendored
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"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--",
|
||||
"tokenAudience": "--token-audience"
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"galleryEndpoint":"https://portal.local.azurestack.external:30015/",
|
||||
"graphEndpoint":"https://graph.windows.net/",
|
||||
"portalEndpoint":"https://portal.local.azurestack.external/",
|
||||
"authentication":{
|
||||
"loginEndpoint":"https://login.windows.net/",
|
||||
"audiences":[
|
||||
"https://management.azurestackci04.onmicrosoft.com/3ee899c8-c137-4ce4-b230-619961a09a73"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,403 +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"
|
||||
"github.com/Azure/go-autorest/version"
|
||||
)
|
||||
|
||||
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", version.UserAgent(), 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", version.UserAgent(), 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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
package mocks
|
||||
|
||||
// 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"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// TestAuthorizationHeader is a faux HTTP Authorization header value
|
||||
TestAuthorizationHeader = "BEARER SECRETTOKEN"
|
||||
|
||||
// TestBadURL is a malformed URL
|
||||
TestBadURL = " "
|
||||
|
||||
// TestDelay is the Retry-After delay used in tests.
|
||||
TestDelay = 0 * time.Second
|
||||
|
||||
// TestHeader is the header used in tests.
|
||||
TestHeader = "x-test-header"
|
||||
|
||||
// TestURL is the URL used in tests.
|
||||
TestURL = "https://microsoft.com/a/b/c/"
|
||||
|
||||
// TestAzureAsyncURL is a URL used in Azure asynchronous tests
|
||||
TestAzureAsyncURL = "https://microsoft.com/a/b/c/async"
|
||||
|
||||
// TestLocationURL is a URL used in Azure asynchronous tests
|
||||
TestLocationURL = "https://microsoft.com/a/b/c/location"
|
||||
)
|
||||
|
||||
const (
|
||||
headerLocation = "Location"
|
||||
headerRetryAfter = "Retry-After"
|
||||
)
|
||||
|
||||
// NewRequest instantiates a new request.
|
||||
func NewRequest() *http.Request {
|
||||
return NewRequestWithContent("")
|
||||
}
|
||||
|
||||
// NewRequestWithContent instantiates a new request using the passed string for the body content.
|
||||
func NewRequestWithContent(c string) *http.Request {
|
||||
r, _ := http.NewRequest("GET", "https://microsoft.com/a/b/c/", NewBody(c))
|
||||
return r
|
||||
}
|
||||
|
||||
// NewRequestWithCloseBody instantiates a new request.
|
||||
func NewRequestWithCloseBody() *http.Request {
|
||||
return NewRequestWithCloseBodyContent("request body")
|
||||
}
|
||||
|
||||
// NewRequestWithCloseBodyContent instantiates a new request using the passed string for the body content.
|
||||
func NewRequestWithCloseBodyContent(c string) *http.Request {
|
||||
r, _ := http.NewRequest("GET", "https://microsoft.com/a/b/c/", NewBodyClose(c))
|
||||
return r
|
||||
}
|
||||
|
||||
// NewRequestForURL instantiates a new request using the passed URL.
|
||||
func NewRequestForURL(u string) *http.Request {
|
||||
return NewRequestWithParams("GET", u, NewBody(""))
|
||||
}
|
||||
|
||||
// NewRequestWithParams instantiates a new request using the provided parameters.
|
||||
func NewRequestWithParams(method, u string, body io.Reader) *http.Request {
|
||||
r, err := http.NewRequest(method, u, body)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("mocks: ERROR (%v) parsing testing URL %s", err, u))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewResponse instantiates a new response.
|
||||
func NewResponse() *http.Response {
|
||||
return NewResponseWithContent("")
|
||||
}
|
||||
|
||||
// NewResponseWithContent instantiates a new response with the passed string as the body content.
|
||||
func NewResponseWithContent(c string) *http.Response {
|
||||
return &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "HTTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Body: NewBody(c),
|
||||
Request: NewRequest(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewResponseWithStatus instantiates a new response using the passed string and integer as the
|
||||
// status and status code.
|
||||
func NewResponseWithStatus(s string, c int) *http.Response {
|
||||
resp := NewResponse()
|
||||
resp.Status = s
|
||||
resp.StatusCode = c
|
||||
return resp
|
||||
}
|
||||
|
||||
// NewResponseWithBodyAndStatus instantiates a new response using the specified mock body,
|
||||
// status and status code
|
||||
func NewResponseWithBodyAndStatus(body *Body, c int, s string) *http.Response {
|
||||
resp := NewResponse()
|
||||
resp.Body = body
|
||||
resp.ContentLength = body.Length()
|
||||
resp.Status = s
|
||||
resp.StatusCode = c
|
||||
return resp
|
||||
}
|
||||
|
||||
// SetResponseHeader adds a header to the passed response.
|
||||
func SetResponseHeader(resp *http.Response, h string, v string) {
|
||||
if resp.Header == nil {
|
||||
resp.Header = make(http.Header)
|
||||
}
|
||||
resp.Header.Set(h, v)
|
||||
}
|
||||
|
||||
// SetResponseHeaderValues adds a header containing all the passed string values.
|
||||
func SetResponseHeaderValues(resp *http.Response, h string, values []string) {
|
||||
if resp.Header == nil {
|
||||
resp.Header = make(http.Header)
|
||||
}
|
||||
for _, v := range values {
|
||||
resp.Header.Add(h, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAcceptedHeaders adds the headers usually associated with a 202 Accepted response.
|
||||
func SetAcceptedHeaders(resp *http.Response) {
|
||||
SetLocationHeader(resp, TestURL)
|
||||
SetRetryHeader(resp, TestDelay)
|
||||
}
|
||||
|
||||
// SetLocationHeader adds the Location header.
|
||||
func SetLocationHeader(resp *http.Response, location string) {
|
||||
SetResponseHeader(resp, http.CanonicalHeaderKey(headerLocation), location)
|
||||
}
|
||||
|
||||
// SetRetryHeader adds the Retry-After header.
|
||||
func SetRetryHeader(resp *http.Response, delay time.Duration) {
|
||||
SetResponseHeader(resp, http.CanonicalHeaderKey(headerRetryAfter), fmt.Sprintf("%v", delay.Seconds()))
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
/*
|
||||
Package mocks provides mocks and helpers used in testing.
|
||||
*/
|
||||
package mocks
|
||||
|
||||
// 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"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Body implements acceptable body over a string.
|
||||
type Body struct {
|
||||
s string
|
||||
b []byte
|
||||
isOpen bool
|
||||
closeAttempts int
|
||||
}
|
||||
|
||||
// NewBody creates a new instance of Body.
|
||||
func NewBody(s string) *Body {
|
||||
return (&Body{s: s}).reset()
|
||||
}
|
||||
|
||||
// NewBodyClose creates a new instance of Body.
|
||||
func NewBodyClose(s string) *Body {
|
||||
return &Body{s: s}
|
||||
}
|
||||
|
||||
// Read reads into the passed byte slice and returns the bytes read.
|
||||
func (body *Body) Read(b []byte) (n int, err error) {
|
||||
if !body.IsOpen() {
|
||||
return 0, fmt.Errorf("ERROR: Body has been closed")
|
||||
}
|
||||
if len(body.b) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(b, body.b)
|
||||
body.b = body.b[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Close closes the body.
|
||||
func (body *Body) Close() error {
|
||||
if body.isOpen {
|
||||
body.isOpen = false
|
||||
body.closeAttempts++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseAttempts returns the number of times Close was called.
|
||||
func (body *Body) CloseAttempts() int {
|
||||
return body.closeAttempts
|
||||
}
|
||||
|
||||
// IsOpen returns true if the Body has not been closed, false otherwise.
|
||||
func (body *Body) IsOpen() bool {
|
||||
return body.isOpen
|
||||
}
|
||||
|
||||
func (body *Body) reset() *Body {
|
||||
body.isOpen = true
|
||||
body.b = []byte(body.s)
|
||||
return body
|
||||
}
|
||||
|
||||
// Length returns the number of bytes in the body.
|
||||
func (body *Body) Length() int64 {
|
||||
if body == nil {
|
||||
return 0
|
||||
}
|
||||
return int64(len(body.b))
|
||||
}
|
||||
|
||||
type response struct {
|
||||
r *http.Response
|
||||
e error
|
||||
d time.Duration
|
||||
}
|
||||
|
||||
// Sender implements a simple null sender.
|
||||
type Sender struct {
|
||||
attempts int
|
||||
responses []response
|
||||
numResponses int
|
||||
repeatResponse []int
|
||||
err error
|
||||
repeatError int
|
||||
emitErrorAfter int
|
||||
}
|
||||
|
||||
// NewSender creates a new instance of Sender.
|
||||
func NewSender() *Sender {
|
||||
return &Sender{}
|
||||
}
|
||||
|
||||
// Do accepts the passed request and, based on settings, emits a response and possible error.
|
||||
func (c *Sender) Do(r *http.Request) (resp *http.Response, err error) {
|
||||
c.attempts++
|
||||
|
||||
if len(c.responses) > 0 {
|
||||
resp = c.responses[0].r
|
||||
if resp != nil {
|
||||
if b, ok := resp.Body.(*Body); ok {
|
||||
b.reset()
|
||||
}
|
||||
} else {
|
||||
err = c.responses[0].e
|
||||
}
|
||||
time.Sleep(c.responses[0].d)
|
||||
c.repeatResponse[0]--
|
||||
if c.repeatResponse[0] == 0 {
|
||||
c.responses = c.responses[1:]
|
||||
c.repeatResponse = c.repeatResponse[1:]
|
||||
}
|
||||
} else {
|
||||
resp = NewResponse()
|
||||
}
|
||||
if resp != nil {
|
||||
resp.Request = r
|
||||
}
|
||||
|
||||
if c.emitErrorAfter > 0 {
|
||||
c.emitErrorAfter--
|
||||
} else if c.err != nil {
|
||||
err = c.err
|
||||
c.repeatError--
|
||||
if c.repeatError == 0 {
|
||||
c.err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AppendResponse adds the passed http.Response to the response stack.
|
||||
func (c *Sender) AppendResponse(resp *http.Response) {
|
||||
c.AppendAndRepeatResponse(resp, 1)
|
||||
}
|
||||
|
||||
// AppendResponseWithDelay adds the passed http.Response to the response stack with the specified delay.
|
||||
func (c *Sender) AppendResponseWithDelay(resp *http.Response, delay time.Duration) {
|
||||
c.AppendAndRepeatResponseWithDelay(resp, delay, 1)
|
||||
}
|
||||
|
||||
// AppendAndRepeatResponse adds the passed http.Response to the response stack along with a
|
||||
// repeat count. A negative repeat count will return the response for all remaining calls to Do.
|
||||
func (c *Sender) AppendAndRepeatResponse(resp *http.Response, repeat int) {
|
||||
c.appendAndRepeat(response{r: resp}, repeat)
|
||||
}
|
||||
|
||||
// AppendAndRepeatResponseWithDelay adds the passed http.Response to the response stack with the specified
|
||||
// delay along with a repeat count. A negative repeat count will return the response for all remaining calls to Do.
|
||||
func (c *Sender) AppendAndRepeatResponseWithDelay(resp *http.Response, delay time.Duration, repeat int) {
|
||||
c.appendAndRepeat(response{r: resp, d: delay}, repeat)
|
||||
}
|
||||
|
||||
// AppendError adds the passed error to the response stack.
|
||||
func (c *Sender) AppendError(err error) {
|
||||
c.AppendAndRepeatError(err, 1)
|
||||
}
|
||||
|
||||
// AppendAndRepeatError adds the passed error to the response stack along with a repeat
|
||||
// count. A negative repeat count will return the response for all remaining calls to Do.
|
||||
func (c *Sender) AppendAndRepeatError(err error, repeat int) {
|
||||
c.appendAndRepeat(response{e: err}, repeat)
|
||||
}
|
||||
|
||||
func (c *Sender) appendAndRepeat(resp response, repeat int) {
|
||||
if c.responses == nil {
|
||||
c.responses = []response{resp}
|
||||
c.repeatResponse = []int{repeat}
|
||||
} else {
|
||||
c.responses = append(c.responses, resp)
|
||||
c.repeatResponse = append(c.repeatResponse, repeat)
|
||||
}
|
||||
c.numResponses++
|
||||
}
|
||||
|
||||
// Attempts returns the number of times Do was called.
|
||||
func (c *Sender) Attempts() int {
|
||||
return c.attempts
|
||||
}
|
||||
|
||||
// SetError sets the error Do should return.
|
||||
func (c *Sender) SetError(err error) {
|
||||
c.SetAndRepeatError(err, 1)
|
||||
}
|
||||
|
||||
// SetAndRepeatError sets the error Do should return and how many calls to Do will return the error.
|
||||
// A negative repeat value will return the error for all remaining calls to Do.
|
||||
func (c *Sender) SetAndRepeatError(err error, repeat int) {
|
||||
c.err = err
|
||||
c.repeatError = repeat
|
||||
}
|
||||
|
||||
// SetEmitErrorAfter sets the number of attempts to be made before errors are emitted.
|
||||
func (c *Sender) SetEmitErrorAfter(ea int) {
|
||||
c.emitErrorAfter = ea
|
||||
}
|
||||
|
||||
// NumResponses returns the number of responses that have been added to the sender.
|
||||
func (c *Sender) NumResponses() int {
|
||||
return c.numResponses
|
||||
}
|
||||
|
||||
// T is a simple testing struct.
|
||||
type T struct {
|
||||
Name string `json:"name" xml:"Name"`
|
||||
Age int `json:"age" xml:"Age"`
|
||||
}
|
|
@ -1,802 +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 TestWithFormData(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))
|
||||
}
|
||||
|
||||
if expected, got := r.Header.Get(http.CanonicalHeaderKey(headerContentType)), mimeTypeFormPost; expected != got {
|
||||
t.Fatalf("autorest:WithFormData Content Type not set or set to wrong value. Expected %v and got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
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.Host != "bing.com" {
|
||||
t.Fatalf("autorest: Preparing an existing request failed when setting the host (%s)", r.URL)
|
||||
}
|
||||
|
||||
if r.URL.Path != "/search" {
|
||||
t.Fatalf("autorest: Preparing an existing request failed when setting the path (%s)", r.URL.Path)
|
||||
}
|
||||
|
||||
if r.URL.RawQuery != "q=golang" {
|
||||
t.Fatalf("autorest: Preparing an existing request failed when setting the query parameters (%s)", r.URL.RawQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyingRequestWithExistingQueryParameters(t *testing.T) {
|
||||
r, err := Prepare(
|
||||
mocks.NewRequestForURL("https://bing.com"),
|
||||
WithPath("search"),
|
||||
WithQueryParameters(map[string]interface{}{"q": "golang the best"}),
|
||||
WithQueryParameters(map[string]interface{}{"pq": "golang+encoded"}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("autorest: Preparing an existing request returned an error (%v)", err)
|
||||
}
|
||||
|
||||
if r.URL.Host != "bing.com" {
|
||||
t.Fatalf("autorest: Preparing an existing request failed when setting the host (%s)", r.URL)
|
||||
}
|
||||
|
||||
if r.URL.Path != "/search" {
|
||||
t.Fatalf("autorest: Preparing an existing request failed when setting the path (%s)", r.URL.Path)
|
||||
}
|
||||
|
||||
if r.URL.RawQuery != "pq=golang+encoded&q=golang+the+best" {
|
||||
t.Fatalf("autorest: Preparing an existing request failed when setting the query parameters (%s)", r.URL.RawQuery)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -1,904 +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"
|
||||
"context"
|
||||
"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()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
delay := 5 * time.Second
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
start := time.Now()
|
||||
end := time.Now()
|
||||
var err error
|
||||
go func() {
|
||||
req := mocks.NewRequest()
|
||||
req = req.WithContext(ctx)
|
||||
_, err = SendWithSender(client, req,
|
||||
AfterDelay(delay))
|
||||
end = time.Now()
|
||||
wg.Done()
|
||||
}()
|
||||
cancel()
|
||||
wg.Wait()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
if end.Sub(start) >= delay {
|
||||
t.Fatal("autorest: AfterDelay elapsed")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("autorest: AfterDelay didn't 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 := 2, 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(1, 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)
|
||||
}
|
||||
}
|
||||
|
||||
type temporaryError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (te temporaryError) Error() string {
|
||||
return te.message
|
||||
}
|
||||
|
||||
func (te temporaryError) Timeout() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (te temporaryError) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestDoRetryForStatusCodes_NilResponseTemporaryError(t *testing.T) {
|
||||
client := mocks.NewSender()
|
||||
client.AppendResponse(nil)
|
||||
client.SetError(temporaryError{message: "faux error"})
|
||||
|
||||
r, err := SendWithSender(client, mocks.NewRequest(),
|
||||
DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...),
|
||||
)
|
||||
|
||||
Respond(r,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
|
||||
if err != nil || client.Attempts() != 2 {
|
||||
t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseTemporaryError -- Got: non-nil error or wrong number of attempts - %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRetryForStatusCodes_NilResponseTemporaryError2(t *testing.T) {
|
||||
client := mocks.NewSender()
|
||||
client.AppendResponse(nil)
|
||||
client.SetError(fmt.Errorf("faux error"))
|
||||
|
||||
r, err := SendWithSender(client, mocks.NewRequest(),
|
||||
DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...),
|
||||
)
|
||||
|
||||
Respond(r,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
|
||||
if err != nil || client.Attempts() != 2 {
|
||||
t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseTemporaryError2 -- Got: nil error or wrong number of attempts - %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type fatalError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (fe fatalError) Error() string {
|
||||
return fe.message
|
||||
}
|
||||
|
||||
func (fe fatalError) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (fe fatalError) Temporary() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestDoRetryForStatusCodes_NilResponseFatalError(t *testing.T) {
|
||||
client := mocks.NewSender()
|
||||
client.AppendResponse(nil)
|
||||
client.SetError(fatalError{"fatal error"})
|
||||
|
||||
r, err := SendWithSender(client, mocks.NewRequest(),
|
||||
DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...),
|
||||
)
|
||||
|
||||
Respond(r,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
|
||||
if err == nil || client.Attempts() > 1 {
|
||||
t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseFatalError -- Got: nil error or wrong number of attempts - %v", err)
|
||||
}
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
package to
|
||||
|
||||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
v := ""
|
||||
if String(&v) != v {
|
||||
t.Fatalf("to: String failed to return the correct string -- expected %v, received %v",
|
||||
v, String(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringHandlesNil(t *testing.T) {
|
||||
if String(nil) != "" {
|
||||
t.Fatalf("to: String failed to correctly convert nil -- expected %v, received %v",
|
||||
"", String(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringPtr(t *testing.T) {
|
||||
v := ""
|
||||
if *StringPtr(v) != v {
|
||||
t.Fatalf("to: StringPtr failed to return the correct string -- expected %v, received %v",
|
||||
v, *StringPtr(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSlice(t *testing.T) {
|
||||
v := []string{}
|
||||
if out := StringSlice(&v); !reflect.DeepEqual(out, v) {
|
||||
t.Fatalf("to: StringSlice failed to return the correct slice -- expected %v, received %v",
|
||||
v, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSliceHandlesNil(t *testing.T) {
|
||||
if out := StringSlice(nil); out != nil {
|
||||
t.Fatalf("to: StringSlice failed to correctly convert nil -- expected %v, received %v",
|
||||
nil, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSlicePtr(t *testing.T) {
|
||||
v := []string{"a", "b"}
|
||||
if out := StringSlicePtr(v); !reflect.DeepEqual(*out, v) {
|
||||
t.Fatalf("to: StringSlicePtr failed to return the correct slice -- expected %v, received %v",
|
||||
v, *out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringMap(t *testing.T) {
|
||||
msp := map[string]*string{"foo": StringPtr("foo"), "bar": StringPtr("bar"), "baz": StringPtr("baz")}
|
||||
for k, v := range StringMap(msp) {
|
||||
if *msp[k] != v {
|
||||
t.Fatalf("to: StringMap incorrectly converted an entry -- expected [%s]%v, received[%s]%v",
|
||||
k, v, k, *msp[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringMapHandlesNil(t *testing.T) {
|
||||
msp := map[string]*string{"foo": StringPtr("foo"), "bar": nil, "baz": StringPtr("baz")}
|
||||
for k, v := range StringMap(msp) {
|
||||
if msp[k] == nil && v != "" {
|
||||
t.Fatalf("to: StringMap incorrectly converted a nil entry -- expected [%s]%v, received[%s]%v",
|
||||
k, v, k, *msp[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringMapPtr(t *testing.T) {
|
||||
ms := map[string]string{"foo": "foo", "bar": "bar", "baz": "baz"}
|
||||
for k, msp := range *StringMapPtr(ms) {
|
||||
if ms[k] != *msp {
|
||||
t.Fatalf("to: StringMapPtr incorrectly converted an entry -- expected [%s]%v, received[%s]%v",
|
||||
k, ms[k], k, *msp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
v := false
|
||||
if Bool(&v) != v {
|
||||
t.Fatalf("to: Bool failed to return the correct string -- expected %v, received %v",
|
||||
v, Bool(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolHandlesNil(t *testing.T) {
|
||||
if Bool(nil) != false {
|
||||
t.Fatalf("to: Bool failed to correctly convert nil -- expected %v, received %v",
|
||||
false, Bool(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolPtr(t *testing.T) {
|
||||
v := false
|
||||
if *BoolPtr(v) != v {
|
||||
t.Fatalf("to: BoolPtr failed to return the correct string -- expected %v, received %v",
|
||||
v, *BoolPtr(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
v := 0
|
||||
if Int(&v) != v {
|
||||
t.Fatalf("to: Int failed to return the correct string -- expected %v, received %v",
|
||||
v, Int(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntHandlesNil(t *testing.T) {
|
||||
if Int(nil) != 0 {
|
||||
t.Fatalf("to: Int failed to correctly convert nil -- expected %v, received %v",
|
||||
0, Int(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntPtr(t *testing.T) {
|
||||
v := 0
|
||||
if *IntPtr(v) != v {
|
||||
t.Fatalf("to: IntPtr failed to return the correct string -- expected %v, received %v",
|
||||
v, *IntPtr(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt32(t *testing.T) {
|
||||
v := int32(0)
|
||||
if Int32(&v) != v {
|
||||
t.Fatalf("to: Int32 failed to return the correct string -- expected %v, received %v",
|
||||
v, Int32(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt32HandlesNil(t *testing.T) {
|
||||
if Int32(nil) != int32(0) {
|
||||
t.Fatalf("to: Int32 failed to correctly convert nil -- expected %v, received %v",
|
||||
0, Int32(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt32Ptr(t *testing.T) {
|
||||
v := int32(0)
|
||||
if *Int32Ptr(v) != v {
|
||||
t.Fatalf("to: Int32Ptr failed to return the correct string -- expected %v, received %v",
|
||||
v, *Int32Ptr(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
v := int64(0)
|
||||
if Int64(&v) != v {
|
||||
t.Fatalf("to: Int64 failed to return the correct string -- expected %v, received %v",
|
||||
v, Int64(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64HandlesNil(t *testing.T) {
|
||||
if Int64(nil) != int64(0) {
|
||||
t.Fatalf("to: Int64 failed to correctly convert nil -- expected %v, received %v",
|
||||
0, Int64(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64Ptr(t *testing.T) {
|
||||
v := int64(0)
|
||||
if *Int64Ptr(v) != v {
|
||||
t.Fatalf("to: Int64Ptr failed to return the correct string -- expected %v, received %v",
|
||||
v, *Int64Ptr(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat32(t *testing.T) {
|
||||
v := float32(0)
|
||||
if Float32(&v) != v {
|
||||
t.Fatalf("to: Float32 failed to return the correct string -- expected %v, received %v",
|
||||
v, Float32(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat32HandlesNil(t *testing.T) {
|
||||
if Float32(nil) != float32(0) {
|
||||
t.Fatalf("to: Float32 failed to correctly convert nil -- expected %v, received %v",
|
||||
0, Float32(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat32Ptr(t *testing.T) {
|
||||
v := float32(0)
|
||||
if *Float32Ptr(v) != v {
|
||||
t.Fatalf("to: Float32Ptr failed to return the correct string -- expected %v, received %v",
|
||||
v, *Float32Ptr(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat64(t *testing.T) {
|
||||
v := float64(0)
|
||||
if Float64(&v) != v {
|
||||
t.Fatalf("to: Float64 failed to return the correct string -- expected %v, received %v",
|
||||
v, Float64(&v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat64HandlesNil(t *testing.T) {
|
||||
if Float64(nil) != float64(0) {
|
||||
t.Fatalf("to: Float64 failed to correctly convert nil -- expected %v, received %v",
|
||||
0, Float64(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat64Ptr(t *testing.T) {
|
||||
v := float64(0)
|
||||
if *Float64Ptr(v) != v {
|
||||
t.Fatalf("to: Float64Ptr failed to return the correct string -- expected %v, received %v",
|
||||
v, *Float64Ptr(v))
|
||||
}
|
||||
}
|
|
@ -1,462 +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"
|
||||
"errors"
|
||||
"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 got, want := String(i), "123"; got != want {
|
||||
t.Logf("got: %q\nwant: %q", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWithStringSlice(t *testing.T) {
|
||||
s := []string{"string1", "string2"}
|
||||
if got, want := String(s, ","), "string1,string2"; got != want {
|
||||
t.Logf("got: %q\nwant: %q", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWithEnum(t *testing.T) {
|
||||
type TestEnumType string
|
||||
s := TestEnumType("string1")
|
||||
if got, want := String(s), "string1"; got != want {
|
||||
t.Logf("got: %q\nwant: %q", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWithEnumSlice(t *testing.T) {
|
||||
type TestEnumType string
|
||||
s := []TestEnumType{"string1", "string2"}
|
||||
if got, want := String(s, ","), "string1,string2"; got != want {
|
||||
t.Logf("got: %q\nwant: %q", got, want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleAsStringSlice() {
|
||||
type TestEnumType string
|
||||
|
||||
a := []TestEnumType{"value1", "value2"}
|
||||
b, _ := AsStringSlice(a)
|
||||
for _, c := range b {
|
||||
fmt.Println(c)
|
||||
}
|
||||
// Output:
|
||||
// value1
|
||||
// value2
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
type someTempError struct{}
|
||||
|
||||
func (s someTempError) Error() string {
|
||||
return "temporary error"
|
||||
}
|
||||
func (s someTempError) Timeout() bool {
|
||||
return true
|
||||
}
|
||||
func (s someTempError) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestIsTemporaryNetworkErrorTrue(t *testing.T) {
|
||||
if !IsTemporaryNetworkError(someTempError{}) {
|
||||
t.Fatal("expected someTempError to be a temporary network error")
|
||||
}
|
||||
if !IsTemporaryNetworkError(errors.New("non-temporary network error")) {
|
||||
t.Fatal("expected random error to be a temporary network error")
|
||||
}
|
||||
}
|
||||
|
||||
type someFatalError struct{}
|
||||
|
||||
func (s someFatalError) Error() string {
|
||||
return "fatal error"
|
||||
}
|
||||
func (s someFatalError) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
func (s someFatalError) Temporary() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestIsTemporaryNetworkErrorFalse(t *testing.T) {
|
||||
if IsTemporaryNetworkError(someFatalError{}) {
|
||||
t.Fatal("expected someFatalError to be a fatal network error")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package utils
|
||||
|
||||
// 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"
|
||||
"os"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
// GetAuthorizer gets an Azure Service Principal authorizer.
|
||||
// This func assumes "AZURE_TENANT_ID", "AZURE_CLIENT_ID",
|
||||
// "AZURE_CLIENT_SECRET" are set as environment variables.
|
||||
//
|
||||
// Deprecated: Use ClientCredentialsConfig.Authorizer() from the
|
||||
// github.com/Azure/go-autorest/autorest/azure/auth package instead.
|
||||
func GetAuthorizer(env azure.Environment) (*autorest.BearerAuthorizer, error) {
|
||||
tenantID := GetEnvVarOrExit("AZURE_TENANT_ID")
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientID := GetEnvVarOrExit("AZURE_CLIENT_ID")
|
||||
clientSecret := GetEnvVarOrExit("AZURE_CLIENT_SECRET")
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, env.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return autorest.NewBearerAuthorizer(spToken), nil
|
||||
}
|
||||
|
||||
// GetEnvVarOrExit returns the value of specified environment variable or terminates if it's not defined.
|
||||
//
|
||||
// Deprecated: This has been supreseeded by the github.com/Azure/go-autorest/autorest/azure/auth package.
|
||||
func GetEnvVarOrExit(varName string) string {
|
||||
value := os.Getenv(varName)
|
||||
if value == "" {
|
||||
fmt.Printf("Missing environment variable %s\n", varName)
|
||||
os.Exit(1)
|
||||
}
|
||||
return value
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package utils
|
||||
|
||||
// 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"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// GetCommit returns git HEAD (short)
|
||||
func GetCommit() string {
|
||||
cmd := exec.Command("git", "rev-parse", "HEAD")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(out.Bytes()[:7])
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,185 +0,0 @@
|
|||
package logger
|
||||
|
||||
// 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"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNilLogger(t *testing.T) {
|
||||
// verify no crash with no logging
|
||||
Instance.WriteRequest(nil, Filter{})
|
||||
}
|
||||
|
||||
const (
|
||||
reqURL = "https://fakething/dot/com"
|
||||
reqHeaderKey = "x-header"
|
||||
reqHeaderVal = "value"
|
||||
reqBody = "the request body"
|
||||
respHeaderKey = "response-header"
|
||||
respHeaderVal = "something"
|
||||
respBody = "the response body"
|
||||
logFileTimeStampRegex = `\(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}(-\d{2}:\d{2}|Z)\)`
|
||||
)
|
||||
|
||||
func TestLogReqRespNoBody(t *testing.T) {
|
||||
err := os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "info")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to set log level: %v", err)
|
||||
}
|
||||
lf := path.Join(os.TempDir(), "testloggingbasic.log")
|
||||
err = os.Setenv("AZURE_GO_SDK_LOG_FILE", lf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to set log file: %v", err)
|
||||
}
|
||||
initDefaultLogger()
|
||||
if Level() != LogInfo {
|
||||
t.Fatalf("wrong log level: %d", Level())
|
||||
}
|
||||
// create mock request and response for logging
|
||||
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create mock request: %v", err)
|
||||
}
|
||||
req.Header.Add(reqHeaderKey, reqHeaderVal)
|
||||
Instance.WriteRequest(req, Filter{})
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "HTTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Request: req,
|
||||
Header: http.Header{},
|
||||
}
|
||||
resp.Header.Add(respHeaderKey, respHeaderVal)
|
||||
Instance.WriteResponse(resp, Filter{})
|
||||
if fl, ok := Instance.(fileLogger); ok {
|
||||
fl.logFile.Close()
|
||||
} else {
|
||||
t.Fatal("expected Instance to be fileLogger")
|
||||
}
|
||||
// parse log file to ensure contents match
|
||||
b, err := ioutil.ReadFile(lf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read log file: %v", err)
|
||||
}
|
||||
parts := strings.Split(string(b), "\n")
|
||||
reqMatch := fmt.Sprintf("%s INFO: REQUEST: %s %s", logFileTimeStampRegex, req.Method, req.URL.String())
|
||||
respMatch := fmt.Sprintf("%s INFO: RESPONSE: %d %s", logFileTimeStampRegex, resp.StatusCode, resp.Request.URL.String())
|
||||
if !matchRegex(t, reqMatch, parts[0]) {
|
||||
t.Fatalf("request header doesn't match: %s", parts[0])
|
||||
}
|
||||
if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", reqHeaderKey, reqHeaderVal), parts[1]) {
|
||||
t.Fatalf("request header entry doesn't match: %s", parts[1])
|
||||
}
|
||||
if !matchRegex(t, respMatch, parts[2]) {
|
||||
t.Fatalf("response header doesn't match: %s", parts[2])
|
||||
}
|
||||
if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", respHeaderKey, respHeaderVal), parts[3]) {
|
||||
t.Fatalf("response header value doesn't match: %s", parts[3])
|
||||
}
|
||||
// disable logging
|
||||
err = os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to clear log level: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogReqRespWithBody(t *testing.T) {
|
||||
err := os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "debug")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to set log level: %v", err)
|
||||
}
|
||||
lf := path.Join(os.TempDir(), "testloggingfull.log")
|
||||
err = os.Setenv("AZURE_GO_SDK_LOG_FILE", lf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to set log file: %v", err)
|
||||
}
|
||||
initDefaultLogger()
|
||||
if Level() != LogDebug {
|
||||
t.Fatalf("wrong log level: %d", Level())
|
||||
}
|
||||
// create mock request and response for logging
|
||||
req, err := http.NewRequest(http.MethodGet, reqURL, strings.NewReader(reqBody))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create mock request: %v", err)
|
||||
}
|
||||
req.Header.Add(reqHeaderKey, reqHeaderVal)
|
||||
Instance.WriteRequest(req, Filter{})
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "HTTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Request: req,
|
||||
Header: http.Header{},
|
||||
Body: ioutil.NopCloser(strings.NewReader(respBody)),
|
||||
}
|
||||
resp.Header.Add(respHeaderKey, respHeaderVal)
|
||||
Instance.WriteResponse(resp, Filter{})
|
||||
if fl, ok := Instance.(fileLogger); ok {
|
||||
fl.logFile.Close()
|
||||
} else {
|
||||
t.Fatal("expected Instance to be fileLogger")
|
||||
}
|
||||
// parse log file to ensure contents match
|
||||
b, err := ioutil.ReadFile(lf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read log file: %v", err)
|
||||
}
|
||||
parts := strings.Split(string(b), "\n")
|
||||
reqMatch := fmt.Sprintf("%s INFO: REQUEST: %s %s", logFileTimeStampRegex, req.Method, req.URL.String())
|
||||
respMatch := fmt.Sprintf("%s INFO: RESPONSE: %d %s", logFileTimeStampRegex, resp.StatusCode, resp.Request.URL.String())
|
||||
if !matchRegex(t, reqMatch, parts[0]) {
|
||||
t.Fatalf("request header doesn't match: %s", parts[0])
|
||||
}
|
||||
if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", reqHeaderKey, reqHeaderVal), parts[1]) {
|
||||
t.Fatalf("request header value doesn't match: %s", parts[1])
|
||||
}
|
||||
if !matchRegex(t, reqBody, parts[2]) {
|
||||
t.Fatalf("request body doesn't match: %s", parts[2])
|
||||
}
|
||||
if !matchRegex(t, respMatch, parts[3]) {
|
||||
t.Fatalf("response header doesn't match: %s", parts[3])
|
||||
}
|
||||
if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", respHeaderKey, respHeaderVal), parts[4]) {
|
||||
t.Fatalf("response header value doesn't match: %s", parts[4])
|
||||
}
|
||||
if !matchRegex(t, respBody, parts[5]) {
|
||||
t.Fatalf("response body doesn't match: %s", parts[5])
|
||||
}
|
||||
// disable logging
|
||||
err = os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to clear log level: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func matchRegex(t *testing.T, pattern, s string) bool {
|
||||
match, err := regexp.MatchString(pattern, s)
|
||||
if err != nil {
|
||||
t.Fatalf("regexp failure: %v", err)
|
||||
}
|
||||
return match
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"clientId": "client-id-123",
|
||||
"clientSecret": "client-secret-456",
|
||||
"subscriptionId": "sub-id-789",
|
||||
"tenantId": "tenant-id-123",
|
||||
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
|
||||
"resourceManagerEndpointUrl": "https://management.azure.com/",
|
||||
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
|
||||
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
|
||||
"galleryEndpointUrl": "https://gallery.azure.com/",
|
||||
"managementEndpointUrl": "https://management.core.windows.net/"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
*.exe
|
|
@ -0,0 +1 @@
|
|||
*.swp
|
|
@ -0,0 +1,6 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
layout: code-of-conduct
|
||||
version: v1.0
|
||||
---
|
||||
|
||||
This code of conduct outlines our expectations for participants within the **NYTimes/gziphandler** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community.
|
||||
|
||||
Our open source community strives to:
|
||||
|
||||
* **Be friendly and patient.**
|
||||
* **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
|
||||
* **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
|
||||
* **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
|
||||
* **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable.
|
||||
* **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
|
||||
|
||||
## Definitions
|
||||
|
||||
Harassment includes, but is not limited to:
|
||||
|
||||
- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation
|
||||
- Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment
|
||||
- Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle
|
||||
- Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop
|
||||
- Threats of violence, both physical and psychological
|
||||
- Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm
|
||||
- Deliberate intimidation
|
||||
- Stalking or following
|
||||
- Harassing photography or recording, including logging online activity for harassment purposes
|
||||
- Sustained disruption of discussion
|
||||
- Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour
|
||||
- Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others
|
||||
- Continued one-on-one communication after requests to cease
|
||||
- Deliberate “outing” of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse
|
||||
- Publication of non-harassing private communication
|
||||
|
||||
Our open source community prioritizes marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding:
|
||||
|
||||
- ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’
|
||||
- Reasonable communication of boundaries, such as “leave me alone,” “go away,” or “I’m not discussing this with you”
|
||||
- Refusal to explain or debate social justice concepts
|
||||
- Communicating in a ‘tone’ you don’t find congenial
|
||||
- Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions
|
||||
|
||||
|
||||
### Diversity Statement
|
||||
|
||||
We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong.
|
||||
|
||||
Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected
|
||||
characteristics above, including participants with disabilities.
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via **code@nytimes.com**. All reports will be handled with discretion. In your report please include:
|
||||
|
||||
- Your contact information.
|
||||
- Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please
|
||||
include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link.
|
||||
- Any additional information that may be helpful.
|
||||
|
||||
After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse.
|
||||
|
||||
### Attribution & Acknowledgements
|
||||
|
||||
We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration:
|
||||
|
||||
* [Django](https://www.djangoproject.com/conduct/reporting/)
|
||||
* [Python](https://www.python.org/community/diversity/)
|
||||
* [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct)
|
||||
* [Contributor Covenant](http://contributor-covenant.org/)
|
||||
* [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/)
|
||||
* [Citizen Code of Conduct](http://citizencodeofconduct.org/)
|
||||
|
||||
This Code of Conduct was based on https://github.com/todogroup/opencodeofconduct
|
|
@ -0,0 +1,30 @@
|
|||
# Contributing to NYTimes/gziphandler
|
||||
|
||||
This is an open source project started by handful of developers at The New York Times and open to the entire Go community.
|
||||
|
||||
We really appreciate your help!
|
||||
|
||||
## Filing issues
|
||||
|
||||
When filing an issue, make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
## Contributing code
|
||||
|
||||
Before submitting changes, please follow these guidelines:
|
||||
|
||||
1. Check the open issues and pull requests for existing discussions.
|
||||
2. Open an issue to discuss a new feature.
|
||||
3. Write tests.
|
||||
4. Make sure code follows the ['Go Code Review Comments'](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
5. Make sure your changes pass `go test`.
|
||||
6. Make sure the entire test suite passes locally and on Travis CI.
|
||||
7. Open a Pull Request.
|
||||
8. [Squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) after receiving feedback and add a [great commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
Unless otherwise noted, the gziphandler source files are distributed under the Apache 2.0-style license found in the LICENSE.md file.
|
|
@ -0,0 +1,29 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
*.out
|
||||
*.tmp
|
||||
tags
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- tip
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
|
||||
script:
|
||||
- ./_test.sh
|
|
@ -0,0 +1 @@
|
|||
logrus
|
|
@ -0,0 +1,13 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
env:
|
||||
- GOMAXPROCS=4 GORACE=halt_on_error=1
|
||||
install:
|
||||
- go get github.com/stretchr/testify/assert
|
||||
- go get gopkg.in/gemnasium/logrus-airbrake-hook.v2
|
||||
- go get golang.org/x/sys/unix
|
||||
- go get golang.org/x/sys/windows
|
||||
script:
|
||||
- go test -race -v ./...
|
|
@ -127,12 +127,10 @@ func (entry Entry) log(level Level, msg string) {
|
|||
}
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
// race conditions will occur when using multiple goroutines
|
||||
func (entry Entry) fireHooks() {
|
||||
func (entry *Entry) fireHooks() {
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
err := entry.Logger.Hooks.Fire(entry.Level, &entry)
|
||||
err := entry.Logger.Hooks.Fire(entry.Level, entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
wmi
|
||||
===
|
||||
|
||||
Package wmi provides a WQL interface for WMI on Windows.
|
|
@ -0,0 +1,22 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
/metrics.out
|
|
@ -7,7 +7,7 @@ expose application metrics, and profile runtime performance in a flexible manner
|
|||
Current API: [![GoDoc](https://godoc.org/github.com/armon/go-metrics?status.svg)](https://godoc.org/github.com/armon/go-metrics)
|
||||
|
||||
Sinks
|
||||
=====
|
||||
-----
|
||||
|
||||
The `metrics` package makes use of a `MetricSink` interface to support delivery
|
||||
to any type of backend. Currently the following sinks are provided:
|
||||
|
@ -23,8 +23,26 @@ In addition to the sinks, the `InmemSignal` can be used to catch a signal,
|
|||
and dump a formatted output of recent metrics. For example, when a process gets
|
||||
a SIGUSR1, it can dump to stderr recent performance metrics for debugging.
|
||||
|
||||
Labels
|
||||
------
|
||||
|
||||
Most metrics do have an equivalent ending with `WithLabels`, such methods
|
||||
allow to push metrics with labels and use some features of underlying Sinks
|
||||
(ex: translated into Prometheus labels).
|
||||
|
||||
Since some of these labels may increase greatly cardinality of metrics, the
|
||||
library allow to filter labels using a blacklist/whitelist filtering system
|
||||
which is global to all metrics.
|
||||
|
||||
* If `Config.AllowedLabels` is not nil, then only labels specified in this value will be sent to underlying Sink, otherwise, all labels are sent by default.
|
||||
* If `Config.BlockedLabels` is not nil, any label specified in this value will not be sent to underlying Sinks.
|
||||
|
||||
By default, both `Config.AllowedLabels` and `Config.BlockedLabels` are nil, meaning that
|
||||
no tags are filetered at all, but it allow to a user to globally block some tags with high
|
||||
cardinality at application level.
|
||||
|
||||
Examples
|
||||
========
|
||||
--------
|
||||
|
||||
Here is an example of using the package:
|
||||
|
||||
|
@ -70,5 +88,4 @@ When a signal comes in, output like the following will be dumped to stderr:
|
|||
[2014-01-28 14:57:33.04 -0800 PST][G] 'foo': 42.000
|
||||
[2014-01-28 14:57:33.04 -0800 PST][P] 'bar': 30.000
|
||||
[2014-01-28 14:57:33.04 -0800 PST][C] 'baz': Count: 3 Min: 1.000 Mean: 41.000 Max: 80.000 Stddev: 39.509
|
||||
[2014-01-28 14:57:33.04 -0800 PST][S] 'method.wow': Count: 3 Min: 22.000 Mean: 54.667 Max: 100.000 Stddev: 40.513
|
||||
|
||||
[2014-01-28 14:57:33.04 -0800 PST][S] 'method.wow': Count: 3 Min: 22.000 Mean: 54.667 Max: 100.000 Stddev: 40.513
|
|
@ -35,10 +35,11 @@ func (m *Metrics) SetGaugeWithLabels(key []string, val float32, labels []Label)
|
|||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
allowed, labelsFiltered := m.allowMetric(key, labels)
|
||||
if !allowed {
|
||||
return
|
||||
}
|
||||
m.sink.SetGaugeWithLabels(key, val, labels)
|
||||
m.sink.SetGaugeWithLabels(key, val, labelsFiltered)
|
||||
}
|
||||
|
||||
func (m *Metrics) EmitKey(key []string, val float32) {
|
||||
|
@ -48,7 +49,8 @@ func (m *Metrics) EmitKey(key []string, val float32) {
|
|||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
allowed, _ := m.allowMetric(key, nil)
|
||||
if !allowed {
|
||||
return
|
||||
}
|
||||
m.sink.EmitKey(key, val)
|
||||
|
@ -72,10 +74,11 @@ func (m *Metrics) IncrCounterWithLabels(key []string, val float32, labels []Labe
|
|||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
allowed, labelsFiltered := m.allowMetric(key, labels)
|
||||
if !allowed {
|
||||
return
|
||||
}
|
||||
m.sink.IncrCounterWithLabels(key, val, labels)
|
||||
m.sink.IncrCounterWithLabels(key, val, labelsFiltered)
|
||||
}
|
||||
|
||||
func (m *Metrics) AddSample(key []string, val float32) {
|
||||
|
@ -96,10 +99,11 @@ func (m *Metrics) AddSampleWithLabels(key []string, val float32, labels []Label)
|
|||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
allowed, labelsFiltered := m.allowMetric(key, labels)
|
||||
if !allowed {
|
||||
return
|
||||
}
|
||||
m.sink.AddSampleWithLabels(key, val, labels)
|
||||
m.sink.AddSampleWithLabels(key, val, labelsFiltered)
|
||||
}
|
||||
|
||||
func (m *Metrics) MeasureSince(key []string, start time.Time) {
|
||||
|
@ -120,23 +124,45 @@ func (m *Metrics) MeasureSinceWithLabels(key []string, start time.Time, labels [
|
|||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
allowed, labelsFiltered := m.allowMetric(key, labels)
|
||||
if !allowed {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
elapsed := now.Sub(start)
|
||||
msec := float32(elapsed.Nanoseconds()) / float32(m.TimerGranularity)
|
||||
m.sink.AddSampleWithLabels(key, msec, labels)
|
||||
m.sink.AddSampleWithLabels(key, msec, labelsFiltered)
|
||||
}
|
||||
|
||||
// UpdateFilter overwrites the existing filter with the given rules.
|
||||
func (m *Metrics) UpdateFilter(allow, block []string) {
|
||||
m.UpdateFilterAndLabels(allow, block, m.AllowedLabels, m.BlockedLabels)
|
||||
}
|
||||
|
||||
// UpdateFilterAndLabels overwrites the existing filter with the given rules.
|
||||
func (m *Metrics) UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) {
|
||||
m.filterLock.Lock()
|
||||
defer m.filterLock.Unlock()
|
||||
|
||||
m.AllowedPrefixes = allow
|
||||
m.BlockedPrefixes = block
|
||||
|
||||
if allowedLabels == nil {
|
||||
// Having a white list means we take only elements from it
|
||||
m.allowedLabels = nil
|
||||
} else {
|
||||
m.allowedLabels = make(map[string]bool)
|
||||
for _, v := range allowedLabels {
|
||||
m.allowedLabels[v] = true
|
||||
}
|
||||
}
|
||||
m.blockedLabels = make(map[string]bool)
|
||||
for _, v := range blockedLabels {
|
||||
m.blockedLabels[v] = true
|
||||
}
|
||||
m.AllowedLabels = allowedLabels
|
||||
m.BlockedLabels = blockedLabels
|
||||
|
||||
m.filter = iradix.New()
|
||||
for _, prefix := range m.AllowedPrefixes {
|
||||
m.filter, _, _ = m.filter.Insert([]byte(prefix), true)
|
||||
|
@ -146,20 +172,56 @@ func (m *Metrics) UpdateFilter(allow, block []string) {
|
|||
}
|
||||
}
|
||||
|
||||
// labelIsAllowed return true if a should be included in metric
|
||||
// the caller should lock m.filterLock while calling this method
|
||||
func (m *Metrics) labelIsAllowed(label *Label) bool {
|
||||
labelName := (*label).Name
|
||||
if m.blockedLabels != nil {
|
||||
_, ok := m.blockedLabels[labelName]
|
||||
if ok {
|
||||
// If present, let's remove this label
|
||||
return false
|
||||
}
|
||||
}
|
||||
if m.allowedLabels != nil {
|
||||
_, ok := m.allowedLabels[labelName]
|
||||
return ok
|
||||
}
|
||||
// Allow by default
|
||||
return true
|
||||
}
|
||||
|
||||
// filterLabels return only allowed labels
|
||||
// the caller should lock m.filterLock while calling this method
|
||||
func (m *Metrics) filterLabels(labels []Label) []Label {
|
||||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
toReturn := labels[:0]
|
||||
for _, label := range labels {
|
||||
if m.labelIsAllowed(&label) {
|
||||
toReturn = append(toReturn, label)
|
||||
}
|
||||
}
|
||||
return toReturn
|
||||
}
|
||||
|
||||
// Returns whether the metric should be allowed based on configured prefix filters
|
||||
func (m *Metrics) allowMetric(key []string) bool {
|
||||
// Also return the applicable labels
|
||||
func (m *Metrics) allowMetric(key []string, labels []Label) (bool, []Label) {
|
||||
m.filterLock.RLock()
|
||||
defer m.filterLock.RUnlock()
|
||||
|
||||
if m.filter == nil || m.filter.Len() == 0 {
|
||||
return m.Config.FilterDefault
|
||||
return m.Config.FilterDefault, m.filterLabels(labels)
|
||||
}
|
||||
|
||||
_, allowed, ok := m.filter.Root().LongestPrefix([]byte(strings.Join(key, ".")))
|
||||
if !ok {
|
||||
return m.Config.FilterDefault
|
||||
return m.Config.FilterDefault, m.filterLabels(labels)
|
||||
}
|
||||
return allowed.(bool)
|
||||
|
||||
return allowed.(bool), m.filterLabels(labels)
|
||||
}
|
||||
|
||||
// Periodically collects runtime stats to publish
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// +build go1.3
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
|
@ -100,7 +101,7 @@ func (p *PrometheusSink) Collect(c chan<- prometheus.Metric) {
|
|||
}
|
||||
}
|
||||
|
||||
var forbiddenChars = regexp.MustCompile("[ .=\\-]")
|
||||
var forbiddenChars = regexp.MustCompile("[ .=\\-/]")
|
||||
|
||||
func (p *PrometheusSink) flattenKey(parts []string, labels []metrics.Label) (string, string) {
|
||||
key := strings.Join(parts, "_")
|
||||
|
|
|
@ -23,6 +23,8 @@ type Config struct {
|
|||
|
||||
AllowedPrefixes []string // A list of metric prefixes to allow, with '.' as the separator
|
||||
BlockedPrefixes []string // A list of metric prefixes to block, with '.' as the separator
|
||||
AllowedLabels []string // A list of metric labels to allow, with '.' as the separator
|
||||
BlockedLabels []string // A list of metric labels to block, with '.' as the separator
|
||||
FilterDefault bool // Whether to allow metrics by default
|
||||
}
|
||||
|
||||
|
@ -30,10 +32,12 @@ type Config struct {
|
|||
// be used to emit
|
||||
type Metrics struct {
|
||||
Config
|
||||
lastNumGC uint32
|
||||
sink MetricSink
|
||||
filter *iradix.Tree
|
||||
filterLock sync.RWMutex
|
||||
lastNumGC uint32
|
||||
sink MetricSink
|
||||
filter *iradix.Tree
|
||||
allowedLabels map[string]bool
|
||||
blockedLabels map[string]bool
|
||||
filterLock sync.RWMutex // Lock filters and allowedLabels/blockedLabels access
|
||||
}
|
||||
|
||||
// Shared global metrics instance
|
||||
|
@ -68,7 +72,7 @@ func New(conf *Config, sink MetricSink) (*Metrics, error) {
|
|||
met := &Metrics{}
|
||||
met.Config = *conf
|
||||
met.sink = sink
|
||||
met.UpdateFilter(conf.AllowedPrefixes, conf.BlockedPrefixes)
|
||||
met.UpdateFilterAndLabels(conf.AllowedPrefixes, conf.BlockedPrefixes, conf.AllowedLabels, conf.BlockedLabels)
|
||||
|
||||
// Start the runtime collector
|
||||
if conf.EnableRuntimeMetrics {
|
||||
|
@ -127,3 +131,11 @@ func MeasureSinceWithLabels(key []string, start time.Time, labels []Label) {
|
|||
func UpdateFilter(allow, block []string) {
|
||||
globalMetrics.Load().(*Metrics).UpdateFilter(allow, block)
|
||||
}
|
||||
|
||||
// UpdateFilterAndLabels set allow/block prefixes of metrics while allowedLabels
|
||||
// and blockedLabels - when not nil - allow filtering of labels in order to
|
||||
// block/allow globally labels (especially useful when having large number of
|
||||
// values for a given label). See README.md for more information about usage.
|
||||
func UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) {
|
||||
globalMetrics.Load().(*Metrics).UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
|
@ -0,0 +1,3 @@
|
|||
language: go
|
||||
go:
|
||||
- tip
|
|
@ -44,13 +44,13 @@ func (n *node) addEdge(e edge) {
|
|||
n.edges.Sort()
|
||||
}
|
||||
|
||||
func (n *node) replaceEdge(e edge) {
|
||||
func (n *node) updateEdge(label byte, node *node) {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= e.label
|
||||
return n.edges[i].label >= label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == e.label {
|
||||
n.edges[idx].node = e.node
|
||||
if idx < num && n.edges[idx].label == label {
|
||||
n.edges[idx].node = node
|
||||
return
|
||||
}
|
||||
panic("replacing missing edge")
|
||||
|
@ -198,10 +198,7 @@ func (t *Tree) Insert(s string, v interface{}) (interface{}, bool) {
|
|||
child := &node{
|
||||
prefix: search[:commonPrefix],
|
||||
}
|
||||
parent.replaceEdge(edge{
|
||||
label: search[0],
|
||||
node: child,
|
||||
})
|
||||
parent.updateEdge(search[0], child)
|
||||
|
||||
// Restore the existing node
|
||||
child.addEdge(edge{
|
||||
|
@ -292,6 +289,53 @@ DELETE:
|
|||
return leaf.val, true
|
||||
}
|
||||
|
||||
// DeletePrefix is used to delete the subtree under a prefix
|
||||
// Returns how many nodes were deleted
|
||||
// Use this to delete large subtrees efficiently
|
||||
func (t *Tree) DeletePrefix(s string) int {
|
||||
return t.deletePrefix(nil, t.root, s)
|
||||
}
|
||||
|
||||
// delete does a recursive deletion
|
||||
func (t *Tree) deletePrefix(parent, n *node, prefix string) int {
|
||||
// Check for key exhaustion
|
||||
if len(prefix) == 0 {
|
||||
// Remove the leaf node
|
||||
subTreeSize := 0
|
||||
//recursively walk from all edges of the node to be deleted
|
||||
recursiveWalk(n, func(s string, v interface{}) bool {
|
||||
subTreeSize++
|
||||
return false
|
||||
})
|
||||
if n.isLeaf() {
|
||||
n.leaf = nil
|
||||
}
|
||||
n.edges = nil // deletes the entire subtree
|
||||
|
||||
// Check if we should merge the parent's other child
|
||||
if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
|
||||
parent.mergeChild()
|
||||
}
|
||||
t.size -= subTreeSize
|
||||
return subTreeSize
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
label := prefix[0]
|
||||
child := n.getEdge(label)
|
||||
if child == nil || (!strings.HasPrefix(child.prefix, prefix) && !strings.HasPrefix(prefix, child.prefix)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if len(child.prefix) > len(prefix) {
|
||||
prefix = prefix[len(prefix):]
|
||||
} else {
|
||||
prefix = prefix[len(child.prefix):]
|
||||
}
|
||||
return t.deletePrefix(n, child, prefix)
|
||||
}
|
||||
|
||||
func (n *node) mergeChild() {
|
||||
e := n.edges[0]
|
||||
child := e.node
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- bwatas@gmail.com
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue