Auto-install and run standalone test proxy server per test package (#21168)

* Auto-install and run standalone test proxy server per test package

* Update recording tests to use standalone proxy

* Simplify proxy binary switch statement

* Add test proxy auto-install docs

* Fix up recording test coverage

* Add StopTestProxy note about go process handling

* Proxy restore/race condition handling. Force ignore PROXY_MANUAL_START in internal tests

* Fix recording readme error handling
This commit is contained in:
Ben Broderick Phillips 2023-07-27 15:58:32 -04:00 коммит произвёл GitHub
Родитель c83d84e92c
Коммит 4dec079c18
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
31 изменённых файлов: 1202 добавлений и 601 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -53,3 +53,6 @@ vendor/
# Default Test Proxy Assets restore directory
.assets
# Default Test Proxy tools install directory
.proxy

Просмотреть файл

@ -135,7 +135,7 @@ Testing is built into the Go toolchain as well with the `testing` library. The t
| playback | `$ENV:AZURE_RECORD_MODE="playback"` | Running tests against recording HTTP interactiosn |
| live | `$ENV:AZURE_RECORD_MODE="live"` | Bypassing test proxy, running against live service, and not recording HTTP interactions (used by live pipelines) |
To get started first [install test-proxy][test_proxy_install] via the standalone executable. Then to start the proxy, from the root of the repository, run the command `test-proxy start`.
By default the [recording](recording_package) package will automatically install and run the test proxy server. If there are issues with auto-install or the proxy needs to be run standalone, it can be run manually instead. To get started first [install test-proxy][test_proxy_install] via the standalone executable, then to start the proxy, from the root of the repository, run the command `test-proxy start`. When invoking tests, set the environment variable `PROXY_MANUAL_START` to `true`.
### Test Mode Options
@ -380,3 +380,4 @@ This creates the pipelines that will verify future PRs. The `azure-sdk-for-go` i
[autorest_intro]: https://github.com/Azure/autorest/blob/main/docs/readme.md
[autorest_directives]: https://github.com/Azure/autorest/blob/main/docs/generate/directives.md
[test_resources]: https://github.com/Azure/azure-sdk-tools/tree/main/eng/common/TestResources
[recording_package]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/internal/recording

Просмотреть файл

@ -4,6 +4,8 @@
### Features Added
* Add support for auto-installing the test proxy standalone tooling in the test recording package
### Breaking Changes
### Bugs Fixed

Просмотреть файл

@ -4,20 +4,37 @@
package perf
import (
"fmt"
"net/http"
"os"
"regexp"
"testing"
"github.com/stretchr/testify/require"
"github.com/Azure/azure-sdk-for-go/sdk/internal/recording"
)
func TestRecordingHTTPClient_Do(t *testing.T) {
// Ignore manual start in pipeline tests, we always want to exercise install
os.Setenv(recording.ProxyManualStartEnv, "false")
proxy, err := recording.StartTestProxy("", nil)
require.NoError(t, err)
defer func() {
err := recording.StopTestProxy(proxy)
if err != nil {
panic(err)
}
}()
req, err := http.NewRequest("POST", "https://www.bing.com", nil)
require.NoError(t, err)
proxyURL := fmt.Sprintf("https://localhost:%d", proxy.Options.ProxyPort)
client := NewProxyTransport(&TransportOptions{
TestName: t.Name(),
proxyURL: "https://localhost:5001/",
proxyURL: proxyURL,
})
require.NotNil(t, client)
@ -32,7 +49,7 @@ func TestRecordingHTTPClient_Do(t *testing.T) {
require.NoError(t, err)
resp, err = client.Do(req)
require.NoError(t, err)
require.Equal(t, "https://localhost:5001", resp.Request.URL.String())
require.Equal(t, proxyURL, resp.Request.URL.String())
require.Contains(t, resp.Request.Header.Get(upstreamURIHeader), "https://www.bing.com")
require.Equal(t, resp.Request.Header.Get(modeHeader), "record")
require.Equal(t, resp.Request.Header.Get(idHeader), client.recID)
@ -42,7 +59,7 @@ func TestRecordingHTTPClient_Do(t *testing.T) {
require.NoError(t, err)
resp, err = client.Do(req)
require.NoError(t, err)
require.Equal(t, "https://localhost:5001", resp.Request.URL.String())
require.Equal(t, proxyURL, resp.Request.URL.String())
require.Contains(t, resp.Request.Header.Get(upstreamURIHeader), "https://www.bing.com")
require.Equal(t, resp.Request.Header.Get(modeHeader), "playback")
require.Equal(t, resp.Request.Header.Get(idHeader), client.recID)

Просмотреть файл

@ -16,9 +16,33 @@ After you've set the `AZURE_RECORD_MODE`, set the `PROXY_CERT` environment varia
$ENV:PROXY_CERT="C:/ <path-to-repo> /azure-sdk-for-go/eng/common/testproxy/dotnet-devcert.crt"
```
## Running the test proxy
Recording and playing back tests relies on the [Test Proxy](https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md) to intercept traffic. The recording package can automatically install and run an instance of the test-proxy server per package. The following code needs to be added to test setup and teardown in order to achieve this:
```golang
func TestMain(m *testing.M) {
proxy, err := recording.StartTestProxy(nil)
if err != nil {
panic(err)
}
... all other test code, including proxy recording setup ...
code := m.Run()
err = recording.StopTestProxy(proxy)
if err != nil {
panic(err)
}
os.Exit(code)
}
```
## Routing Traffic
The first step in instrumenting a client to interact with recorded tests is to direct traffic to the proxy through a custom `policy`. In these examples we'll use testify's [`require`](https://pkg.go.dev/github.com/stretchr/testify/require) library but you can use the framework of your choice. Each test has to call `recording.Start` and `recording.Stop`, the rest is taken care of by the `recording` library and the [`test-proxy`](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy)
The first step in instrumenting a client to interact with recorded tests is to direct traffic to the proxy through a custom `policy`. In these examples we'll use testify's [`require`](https://pkg.go.dev/github.com/stretchr/testify/require) library but you can use the framework of your choice. Each test has to call `recording.Start` and `recording.Stop`, the rest is taken care of by the `recording` library and the [`test-proxy`](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy).
The snippet below demonstrates an example test policy:

Просмотреть файл

@ -9,6 +9,7 @@ package recording
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
@ -76,7 +77,8 @@ func SetDefaultMatcher(t *testing.T, options *SetDefaultMatcherOptions) error {
return nil
}
options.fillOptions()
req, err := http.NewRequest("POST", "http://localhost:5000/Admin/SetMatcher", http.NoBody)
url := fmt.Sprintf("%s/Admin/SetMatcher", defaultOptions().baseURL())
req, err := http.NewRequest("POST", url, http.NoBody)
if err != nil {
panic(err)
}

Просмотреть файл

@ -9,167 +9,197 @@ package recording
import (
"bytes"
"net/http"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
func TestSetBodilessMatcher(t *testing.T) {
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()
err := Start(t, packagePath, nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
client, err := GetHTTPClient(t)
require.NoError(t, err)
_, err = client.Do(req)
require.NoError(t, err)
err = Stop(t, nil)
require.NoError(t, err)
// Run a second request to with different body to verify it works
recordMode = PlaybackMode
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = SetBodilessMatcher(t, nil)
require.NoError(t, err)
req, err = http.NewRequest("POST", "https://localhost:5001", bytes.NewReader([]byte("abcdef")))
require.NoError(t, err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
_, err = client.Do(req)
require.NoError(t, err)
err = Stop(t, nil)
require.NoError(t, err)
err = ResetProxy(nil)
require.NoError(t, err)
type matchersTests struct {
suite.Suite
proxy *TestProxyInstance
}
func TestSetBodilessMatcherNilTest(t *testing.T) {
func TestMatchers(t *testing.T) {
suite.Run(t, new(matchersTests))
}
func (s *matchersTests) SetupSuite() {
// Ignore manual start in pipeline tests, we always want to exercise install
os.Setenv(ProxyManualStartEnv, "false")
proxy, err := StartTestProxy("", nil)
s.proxy = proxy
require.NoError(s.T(), err)
}
func (s *matchersTests) TearDownSuite() {
err1 := StopTestProxy(s.proxy)
err2 := os.RemoveAll("./testdata/recordings/TestMatchers/")
require.NoError(s.T(), err1)
require.NoError(s.T(), err2)
}
func (s *matchersTests) TestSetBodilessMatcher() {
require := require.New(s.T())
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()
err := Start(t, packagePath, nil)
require.NoError(t, err)
err := Start(s.T(), packagePath, nil)
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
_, err = client.Do(req)
require.NoError(t, err)
require.NoError(err)
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Run a second request to with different body to verify it works
recordMode = PlaybackMode
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
err = SetBodilessMatcher(s.T(), nil)
require.NoError(err)
req, err = http.NewRequest("POST", defaultOptions().baseURL(), bytes.NewReader([]byte("abcdef")))
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(s.T()))
_, err = client.Do(req)
require.NoError(err)
err = Stop(s.T(), nil)
require.NoError(err)
err = ResetProxy(nil)
require.NoError(err)
}
func (s *matchersTests) TestSetBodilessMatcherNilTest() {
require := require.New(s.T())
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()
err := Start(s.T(), packagePath, nil)
require.NoError(err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(s.T()))
client, err := GetHTTPClient(s.T())
require.NoError(err)
_, err = client.Do(req)
require.NoError(err)
err = Stop(s.T(), nil)
require.NoError(err)
// Run a second request to with different body to verify it works
recordMode = PlaybackMode
err = Start(s.T(), packagePath, nil)
require.NoError(err)
err = SetBodilessMatcher(nil, nil)
require.NoError(t, err)
require.NoError(err)
req, err = http.NewRequest("POST", "https://localhost:5001", bytes.NewReader([]byte("abcdef")))
require.NoError(t, err)
req, err = http.NewRequest("POST", defaultOptions().baseURL(), bytes.NewReader([]byte("abcdef")))
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
_, err = client.Do(req)
require.NoError(t, err)
require.NoError(err)
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
err = ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
}
func TestSetDefaultMatcher(t *testing.T) {
func (s *matchersTests) TestSetDefaultMatcher() {
require := require.New(s.T())
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()
err := Start(t, packagePath, nil)
require.NoError(t, err)
err := Start(s.T(), packagePath, nil)
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
_, err = client.Do(req)
require.NoError(t, err)
require.NoError(err)
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Run a second request to with different body to verify it works
recordMode = PlaybackMode
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
err = SetDefaultMatcher(nil, &SetDefaultMatcherOptions{ExcludedHeaders: []string{"ExampleHeader"}})
require.NoError(t, err)
require.NoError(err)
req, err = http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err = http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://bing.com")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
req.Header.Set("ExampleHeader", "blah-blah-blah")
err = handleProxyResponse(client.Do(req))
require.NoError(t, err)
require.NoError(err)
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
err = ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
}
func TestAddDefaults(t *testing.T) {
require.Equal(t, 4, len(addDefaults([]string{})))
require.Equal(t, 4, len(addDefaults([]string{":path"})))
require.Equal(t, 4, len(addDefaults([]string{":path", ":authority"})))
require.Equal(t, 4, len(addDefaults([]string{":path", ":authority", ":method"})))
require.Equal(t, 4, len(addDefaults([]string{":path", ":authority", ":method", ":scheme"})))
require.Equal(t, 5, len(addDefaults([]string{":path", ":authority", ":method", ":scheme", "extra"})))
func (s *matchersTests) TestAddDefaults() {
require := require.New(s.T())
require.Equal(4, len(addDefaults([]string{})))
require.Equal(4, len(addDefaults([]string{":path"})))
require.Equal(4, len(addDefaults([]string{":path", ":authority"})))
require.Equal(4, len(addDefaults([]string{":path", ":authority", ":method"})))
require.Equal(4, len(addDefaults([]string{":path", ":authority", ":method", ":scheme"})))
require.Equal(5, len(addDefaults([]string{":path", ":authority", ":method", ":scheme", "extra"})))
}

Просмотреть файл

@ -524,6 +524,7 @@ var client = http.Client{
type RecordingOptions struct {
UseHTTPS bool
ProxyPort int
GroupForReplace string
Variables map[string]interface{}
TestInstance *testing.T
@ -531,7 +532,8 @@ type RecordingOptions struct {
func defaultOptions() *RecordingOptions {
return &RecordingOptions{
UseHTTPS: true,
UseHTTPS: true,
ProxyPort: os.Getpid()%10000 + 20000,
}
}
@ -558,6 +560,10 @@ func (r RecordingOptions) ReplaceAuthority(t *testing.T, rawReq *http.Request) *
}
func (r RecordingOptions) host() string {
if r.ProxyPort != 0 {
return fmt.Sprintf("localhost:%d", r.ProxyPort)
}
if r.UseHTTPS {
return "localhost:5001"
}
@ -589,7 +595,7 @@ func getGitRoot(fromPath string) (string, error) {
root, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("Unable to find git root for path '%s'", absPath)
return "", fmt.Errorf("unable to find git root for path '%s'", absPath)
}
// Wrap with Abs() to get os-specific path separators to support sub-path matching
@ -667,7 +673,6 @@ func requestStart(url string, testId string, assetConfigLocation string) (*http.
return client.Do(req)
}
// Start tells the test proxy to begin accepting requests for a given test
func Start(t *testing.T, pathToRecordings string, options *RecordingOptions) error {
if options == nil {
options = defaultOptions()
@ -789,6 +794,9 @@ func Stop(t *testing.T, options *RecordingOptions) error {
req.Header.Set(IDHeader, recTest.recordingId)
testSuite.Remove(t.Name())
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != 200 {
b, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
@ -940,7 +948,7 @@ func (c RecordingHTTPClient) Do(req *http.Request) (*http.Response, error) {
// NewRecordingHTTPClient returns a type that implements `azcore.Transporter`. This will automatically route tests on the `Do` call.
func NewRecordingHTTPClient(t *testing.T, options *RecordingOptions) (*RecordingHTTPClient, error) {
if options == nil {
options = &RecordingOptions{UseHTTPS: true}
options = defaultOptions()
}
c, err := GetHTTPClient(t)
if err != nil {

Просмотреть файл

@ -24,14 +24,37 @@ import (
"github.com/stretchr/testify/suite"
)
const packagePath = "sdk/internal/recording/testdata"
type recordingTests struct {
suite.Suite
proxy *TestProxyInstance
}
func TestRecording(t *testing.T) {
suite.Run(t, new(recordingTests))
}
func (s *recordingTests) SetupSuite() {
// Ignore manual start in pipeline tests, we always want to exercise install
os.Setenv(ProxyManualStartEnv, "false")
proxy, err := StartTestProxy("", nil)
s.proxy = proxy
require.NoError(s.T(), err)
}
func (s *recordingTests) TearDownSuite() {
stopErr := StopTestProxy(s.proxy)
require.NoError(s.T(), stopErr)
files, err := filepath.Glob("recordings/**/*.yaml")
require.NoError(s.T(), err)
for _, f := range files {
err := os.Remove(f)
require.NoError(s.T(), err)
}
}
func (s *recordingTests) TestInitializeRecording() {
require := require.New(s.T())
context := NewTestContext(func(msg string) { require.FailNow(msg) }, func(msg string) { s.T().Log(msg) }, func() string { return s.T().Name() })
@ -239,7 +262,7 @@ func (s *recordingTests) TestNow() {
require.NoError(err)
}
func (s *recordingTests) TestGenerateAlphaNumericID() {
func (s *recordingTests) TestRecordingGenerateAlphaNumericID() {
require := require.New(s.T())
context := NewTestContext(func(msg string) { require.FailNow(msg) }, func(msg string) { s.T().Log(msg) }, func() string { return s.T().Name() })
@ -370,215 +393,214 @@ func (s *recordingTests) TestRecordRequestsAndFailMatchingForMissingRecording()
require.NoError(err)
}
func (s *recordingTests) TearDownSuite() {
files, err := filepath.Glob("recordings/**/*.yaml")
require.NoError(s.T(), err)
for _, f := range files {
err := os.Remove(f)
require.NoError(s.T(), err)
}
}
func TestGetEnvVariable(t *testing.T) {
require.Equal(t, GetEnvVariable("Nonexistentevnvar", "somefakevalue"), "somefakevalue")
func (s *recordingTests) TestGetEnvVariable() {
require := require.New(s.T())
require.Equal(GetEnvVariable("Nonexistentevnvar", "somefakevalue"), "somefakevalue")
temp := recordMode
recordMode = RecordingMode
t.Setenv("TEST_VARIABLE", "expected")
require.Equal(t, "expected", GetEnvVariable("TEST_VARIABLE", "unexpected"))
s.T().Setenv("TEST_VARIABLE", "expected")
require.Equal("expected", GetEnvVariable("TEST_VARIABLE", "unexpected"))
recordMode = temp
}
func TestRecordingOptions(t *testing.T) {
func (s *recordingTests) TestRecordingOptions() {
require := require.New(s.T())
r := RecordingOptions{
UseHTTPS: true,
}
require.Equal(t, r.baseURL(), "https://localhost:5001")
require.Equal(r.baseURL(), "https://localhost:5001")
r.UseHTTPS = false
require.Equal(t, r.baseURL(), "http://localhost:5000")
require.Equal(r.baseURL(), "http://localhost:5000")
r = *defaultOptions()
require.Equal(r.baseURL(), fmt.Sprintf("https://localhost:%d", r.ProxyPort))
// ProxyPort should be generated deterministically
require.Equal(r.ProxyPort, defaultOptions().ProxyPort)
}
var packagePath = "sdk/internal/recording/testdata"
func TestStartStop(t *testing.T) {
func (s *recordingTests) TestStartStop() {
require := require.New(s.T())
os.Setenv("AZURE_RECORD_MODE", "record")
defer os.Unsetenv("AZURE_RECORD_MODE")
err := Start(t, packagePath, nil)
require.NoError(t, err)
err := Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://www.bing.com/")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open("./testdata/recordings/TestStartStop.json")
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
}
func TestStartStopRecordingClient(t *testing.T) {
func (s *recordingTests) TestStartStopRecordingClient() {
require := require.New(s.T())
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()
err := Start(t, packagePath, nil)
require.NoError(t, err)
err := Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := NewRecordingHTTPClient(t, nil)
require.NoError(t, err)
client, err := NewRecordingHTTPClient(s.T(), nil)
require.NoError(err)
req, err := http.NewRequest("POST", "https://azsdkengsys.azurecr.io/acr/v1/some_registry/_tags", nil)
require.NoError(t, err)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer func() {
err = jsonFile.Close()
require.NoError(t, err)
require.NoError(err)
err = os.Remove(jsonFile.Name())
require.NoError(t, err)
require.NoError(err)
}()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.Equal(t, "https://azsdkengsys.azurecr.io/acr/v1/some_registry/_tags", data.Entries[0].RequestURI)
require.Equal(t, resp.Request.URL.String(), "https://localhost:5001/acr/v1/some_registry/_tags")
require.NoError(err)
require.Equal("https://azsdkengsys.azurecr.io/acr/v1/some_registry/_tags",
data.Entries[0].RequestURI)
require.Equal(resp.Request.URL.String(),
fmt.Sprintf("%s/acr/v1/some_registry/_tags", defaultOptions().baseURL()))
}
func TestStopRecordingNoStart(t *testing.T) {
func (s *recordingTests) TestStopRecordingNoStart() {
require := require.New(s.T())
os.Setenv("AZURE_RECORD_MODE", "record")
defer os.Unsetenv("AZURE_RECORD_MODE")
err := Stop(t, nil)
require.Error(t, err)
err := Stop(s.T(), nil)
require.Error(err)
jsonFile, err := os.Open("./testdata/recordings/TestStopRecordingNoStart.json")
require.Error(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.Error(err)
defer jsonFile.Close()
}
func TestLiveModeOnly(t *testing.T) {
LiveOnly(t)
func (s *recordingTests) TestLiveModeOnly() {
LiveOnly(s.T())
if GetRecordMode() == PlaybackMode {
t.Fatalf("Test should not run in playback")
s.T().Fatalf("Test should not run in playback")
}
}
func TestSleep(t *testing.T) {
func (s *recordingTests) TestSleep() {
start := time.Now()
Sleep(time.Second * 5)
Sleep(time.Millisecond * 100)
duration := time.Since(start)
if GetRecordMode() == PlaybackMode {
if duration > (time.Second * 1) {
t.Fatalf("Sleep took longer than five seconds")
if duration >= (time.Millisecond * 50) {
s.T().Fatalf("Sleep took at least 50ms")
}
} else {
if duration < (time.Second * 1) {
t.Fatalf("Sleep took less than five seconds")
if duration < (time.Second * 50) {
s.T().Fatalf("Sleep took less than 50ms")
}
}
}
func TestBadAzureRecordMode(t *testing.T) {
func (s *recordingTests) TestBadAzureRecordMode() {
require := require.New(s.T())
temp := recordMode
recordMode = "badvalue"
err := Start(t, packagePath, nil)
require.Error(t, err)
err := Start(s.T(), packagePath, nil)
require.Error(err)
recordMode = temp
}
func TestBackwardSlashPath(t *testing.T) {
t.Skip("Temporarily skipping due to changes in test-proxy.")
func (s *recordingTests) TestBackwardSlashPath() {
s.T().Skip("Temporarily skipping due to changes in test-proxy.")
require := require.New(s.T())
os.Setenv("AZURE_RECORD_MODE", "record")
defer os.Unsetenv("AZURE_RECORD_MODE")
packagePathBackslash := "sdk\\internal\\recording\\testdata"
err := Start(t, packagePathBackslash, nil)
require.NoError(t, err)
err := Start(s.T(), packagePathBackslash, nil)
require.NoError(err)
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
}
func TestLiveOnly(t *testing.T) {
require.Equal(t, IsLiveOnly(t), false)
LiveOnly(t)
require.Equal(t, IsLiveOnly(t), true)
func (s *recordingTests) TestLiveOnly() {
require := require.New(s.T())
require.Equal(IsLiveOnly(s.T()), false)
LiveOnly(s.T())
require.Equal(IsLiveOnly(s.T()), true)
}
func TestHostAndScheme(t *testing.T) {
r := RecordingOptions{UseHTTPS: true}
require.Equal(t, r.scheme(), "https")
require.Equal(t, r.host(), "localhost:5001")
r.UseHTTPS = false
require.Equal(t, r.scheme(), "http")
require.Equal(t, r.host(), "localhost:5000")
}
func TestGitRootDetection(t *testing.T) {
func (s *recordingTests) TestGitRootDetection() {
require := require.New(s.T())
cwd, err := os.Getwd()
require.NoError(t, err)
require.NoError(err)
gitRoot, err := getGitRoot(cwd)
require.NoError(t, err)
require.NoError(err)
parentDir := filepath.Dir(gitRoot)
_, err = getGitRoot(parentDir)
require.Error(t, err)
require.Error(err)
}
func TestRecordingAssetConfigNotExist(t *testing.T) {
func (s *recordingTests) TestRecordingAssetConfigNotExist() {
require := require.New(s.T())
absPath, relPath, err := getAssetsConfigLocation(".")
require.NoError(t, err)
require.Equal(t, "", absPath)
require.Equal(t, "", relPath)
require.NoError(err)
require.Equal("", absPath)
require.Equal("", relPath)
}
func TestRecordingAssetConfigOutOfBounds(t *testing.T) {
func (s *recordingTests) TestRecordingAssetConfigOutOfBounds() {
require := require.New(s.T())
cwd, err := os.Getwd()
require.NoError(t, err)
require.NoError(err)
gitRoot, err := getGitRoot(cwd)
require.NoError(t, err)
require.NoError(err)
parentDir := filepath.Dir(gitRoot)
absPath, err := findAssetsConfigFile(parentDir, gitRoot)
require.NoError(t, err)
require.Equal(t, "", absPath)
require.NoError(err)
require.Equal("", absPath)
}
func TestRecordingAssetConfig(t *testing.T) {
func (s *recordingTests) TestRecordingAssetConfig() {
require := require.New(s.T())
cases := []struct{ expectedDirectory, searchDirectory, testFileLocation string }{
{"sdk/internal/recording", "sdk/internal/recording", recordingAssetConfigName},
{"sdk/internal/recording", "sdk/internal/recording/", recordingAssetConfigName},
@ -587,31 +609,32 @@ func TestRecordingAssetConfig(t *testing.T) {
}
cwd, err := os.Getwd()
require.NoError(t, err)
require.NoError(err)
gitRoot, err := getGitRoot(cwd)
require.NoError(t, err)
require.NoError(err)
for _, c := range cases {
_ = os.Remove(c.testFileLocation)
o, err := os.Create(c.testFileLocation)
require.NoError(t, err)
require.NoError(err)
o.Close()
absPath, relPath, err := getAssetsConfigLocation(c.searchDirectory)
// Clean up first in case of an assertion panic
require.NoError(t, os.Remove(c.testFileLocation))
require.NoError(t, err)
require.NoError(os.Remove(c.testFileLocation))
require.NoError(err)
expected := c.expectedDirectory + string(os.PathSeparator) + recordingAssetConfigName
expected = strings.ReplaceAll(expected, "/", string(os.PathSeparator))
require.Equal(t, expected, relPath)
require.Equal(expected, relPath)
absPathExpected := filepath.Join(gitRoot, expected)
require.Equal(t, absPathExpected, absPath)
require.Equal(absPathExpected, absPath)
}
}
func TestFindProxyCertLocation(t *testing.T) {
func (s *recordingTests) TestFindProxyCertLocation() {
require := require.New(s.T())
savedValue, ok := os.LookupEnv("PROXY_CERT")
if ok {
defer os.Setenv("PROXY_CERT", savedValue)
@ -619,120 +642,126 @@ func TestFindProxyCertLocation(t *testing.T) {
if ok {
location, err := findProxyCertLocation()
require.NoError(t, err)
require.Contains(t, location, "dotnet-devcert.crt")
require.NoError(err)
require.Contains(location, "dotnet-devcert.crt")
}
err := os.Unsetenv("PROXY_CERT")
require.NoError(t, err)
require.NoError(err)
location, err := findProxyCertLocation()
require.NoError(t, err)
require.Contains(t, location, filepath.Join("eng", "common", "testproxy", "dotnet-devcert.crt"))
require.NoError(err)
require.Contains(location, filepath.Join("eng", "common", "testproxy", "dotnet-devcert.crt"))
}
func TestVariables(t *testing.T) {
func (s *recordingTests) TestVariables() {
require := require.New(s.T())
temp := recordMode
recordMode = RecordingMode
defer func() { recordMode = temp }()
err := Start(t, packagePath, nil)
require.NoError(t, err)
err := Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := NewRecordingHTTPClient(t, nil)
require.NoError(t, err)
client, err := NewRecordingHTTPClient(s.T(), nil)
require.NoError(err)
req, err := http.NewRequest("POST", "https://azsdkengsys.azurecr.io/acr/v1/some_registry/_tags", nil)
require.NoError(t, err)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, &RecordingOptions{Variables: map[string]interface{}{"key1": "value1", "key2": "1"}})
require.NoError(t, err)
opts := defaultOptions()
opts.Variables = map[string]interface{}{"key1": "value1", "key2": "1"}
err = Stop(s.T(), opts)
require.NoError(err)
recordMode = PlaybackMode
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
variables := GetVariables(t)
require.Equal(t, variables["key1"], "value1")
require.Equal(t, variables["key2"], "1")
variables := GetVariables(s.T())
require.Equal(variables["key1"], "value1")
require.Equal(variables["key2"], "1")
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer func() {
err = jsonFile.Close()
require.NoError(t, err)
require.NoError(err)
err = os.Remove(jsonFile.Name())
require.NoError(t, err)
require.NoError(err)
}()
}
func TestRace(t *testing.T) {
func (s *recordingTests) TestRace() {
require := require.New(s.T())
temp := recordMode
recordMode = LiveMode
t.Cleanup(func() { recordMode = temp })
s.T().Cleanup(func() { recordMode = temp })
for i := 0; i < 4; i++ {
t.Run("", func(t *testing.T) {
s.T().Run("", func(t *testing.T) {
t.Parallel()
err := Start(t, "", nil)
require.NoError(t, err)
require.NoError(err)
GetRecordingId(t)
GetVariables(t)
IsLiveOnly(t)
err = Stop(t, nil)
require.NoError(t, err)
require.NoError(err)
LiveOnly(t)
})
}
}
func Test_generateAlphaNumericID(t *testing.T) {
func (s *recordingTests) TestInnerGenerateAlphaNumericID() {
require := require.New(s.T())
seed1 := int64(1234567)
seed2 := int64(7654321)
randomSource1 := rand.NewSource(seed1)
randomSource2 := rand.NewSource(seed2)
randomSource3 := rand.NewSource(seed2)
rand1, err := generateAlphaNumericID("test", 10, false, randomSource1)
require.NoError(t, err)
require.Equal(t, 10, len(rand1))
require.Equal(t, "test", rand1[0:4])
require.NoError(err)
require.Equal(10, len(rand1))
require.Equal("test", rand1[0:4])
rand2, err := generateAlphaNumericID("test", 10, false, randomSource2)
require.NoError(t, err)
require.NoError(err)
rand3, err := generateAlphaNumericID("test", 10, false, randomSource3)
require.NoError(t, err)
require.Equal(t, rand2, rand3)
require.NotEqual(t, rand1, rand2)
require.NoError(err)
require.Equal(rand2, rand3)
require.NotEqual(rand1, rand2)
}
func TestGenerateAlphaNumericID(t *testing.T) {
func (s *recordingTests) TestGenerateAlphaNumericID() {
require := require.New(s.T())
recordMode = RecordingMode
err := Start(t, packagePath, nil)
require.NoError(t, err)
rand1, err := GenerateAlphaNumericID(t, "test", 10, false)
require.NoError(t, err)
rand2, err := GenerateAlphaNumericID(t, "test", 10, false)
require.NoError(t, err)
require.NotEqual(t, rand1, rand2)
err = Stop(t, nil)
require.NoError(t, err)
err := Start(s.T(), packagePath, nil)
require.NoError(err)
rand1, err := GenerateAlphaNumericID(s.T(), "test", 10, false)
require.NoError(err)
rand2, err := GenerateAlphaNumericID(s.T(), "test", 10, false)
require.NoError(err)
require.NotEqual(rand1, rand2)
err = Stop(s.T(), nil)
require.NoError(err)
recordMode = PlaybackMode
err = Start(t, packagePath, nil)
require.NoError(t, err)
rand3, err := GenerateAlphaNumericID(t, "test", 10, false)
require.NoError(t, err)
rand4, err := GenerateAlphaNumericID(t, "test", 10, false)
require.NoError(t, err)
require.Equal(t, rand1, rand3)
require.Equal(t, rand2, rand4)
err = Stop(t, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
rand3, err := GenerateAlphaNumericID(s.T(), "test", 10, false)
require.NoError(err)
rand4, err := GenerateAlphaNumericID(s.T(), "test", 10, false)
require.NoError(err)
require.Equal(rand1, rand3)
require.Equal(rand2, rand4)
err = Stop(s.T(), nil)
require.NoError(err)
}

Просмотреть файл

@ -25,6 +25,7 @@ import (
type sanitizerTests struct {
suite.Suite
proxy *TestProxyInstance
}
const authHeader string = "Authorization"
@ -36,6 +37,19 @@ func TestRecordingSanitizer(t *testing.T) {
suite.Run(t, new(sanitizerTests))
}
func (s *sanitizerTests) SetupSuite() {
proxy, err := StartTestProxy("", nil)
s.proxy = proxy
require.NoError(s.T(), err)
}
func (s *sanitizerTests) TearDownSuite() {
err1 := StopTestProxy(s.proxy)
err2 := os.RemoveAll("testfiles")
require.NoError(s.T(), err1)
require.NoError(s.T(), err2)
}
func (s *sanitizerTests) TestDefaultSanitizerSanitizesAuthHeader() {
require := require.New(s.T())
server, cleanup := mock.NewServer()
@ -140,13 +154,6 @@ func (s *sanitizerTests) TestAddUrlSanitizerSanitizes() {
}
}
func (s *sanitizerTests) TearDownSuite() {
require := require.New(s.T())
// cleanup test files
err := os.RemoveAll("testfiles")
require.NoError(err)
}
func getTestFileName(t *testing.T, addSuffix bool) string {
name := "testfiles/" + t.Name()
if addSuffix {
@ -191,552 +198,572 @@ func (e Entry) ResponseBodyByValue(k string) interface{} {
return m[k]
}
func TestUriSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestUriSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
srvURL := "http://host.docker.internal:8080/"
err = AddURISanitizer("https://replacement.com/", srvURL, nil)
require.NoError(t, err)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.Equal(t, data.Entries[0].RequestURI, "https://replacement.com/")
require.Equal(data.Entries[0].RequestURI, "https://replacement.com/")
}
func TestHeaderRegexSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestHeaderRegexSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
req.Header.Set("testproxy-header", "fakevalue")
req.Header.Set("FakeStorageLocation", "https://fakeaccount.blob.core.windows.net")
req.Header.Set("ComplexRegex", "https://fakeaccount.table.core.windows.net")
err = AddHeaderRegexSanitizer("testproxy-header", "Sanitized", "", nil)
require.NoError(t, err)
require.NoError(err)
err = AddHeaderRegexSanitizer("FakeStorageLocation", "Sanitized", "https\\:\\/\\/(?<account>[a-z]+)\\.blob\\.core\\.windows\\.net", nil)
require.NoError(t, err)
require.NoError(err)
// This is the only failing one
err = AddHeaderRegexSanitizer("ComplexRegex", "Sanitized", "https\\:\\/\\/(?<account>[a-z]+)\\.(?:table|blob|queue)\\.core\\.windows\\.net", &RecordingOptions{GroupForReplace: "account"})
require.NoError(t, err)
opts := defaultOptions()
opts.GroupForReplace = "account"
err = AddHeaderRegexSanitizer("ComplexRegex", "Sanitized", "https\\:\\/\\/(?<account>[a-z]+)\\.(?:table|blob|queue)\\.core\\.windows\\.net", opts)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.Equal(t, "Sanitized", data.Entries[0].RequestHeaders["testproxy-header"])
require.Equal(t, "Sanitized", data.Entries[0].RequestHeaders["fakestoragelocation"])
require.Equal(t, "https://Sanitized.table.core.windows.net", data.Entries[0].RequestHeaders["complexregex"])
require.Equal("Sanitized", data.Entries[0].RequestHeaders["testproxy-header"])
require.Equal("Sanitized", data.Entries[0].RequestHeaders["fakestoragelocation"])
require.Equal("https://Sanitized.table.core.windows.net", data.Entries[0].RequestHeaders["complexregex"])
}
func TestBodyKeySanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestBodyKeySanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
bodyValue := map[string]string{
"key1": "value1",
}
marshalled, err := json.Marshal(bodyValue)
require.NoError(t, err)
require.NoError(err)
req.Body = io.NopCloser(bytes.NewReader(marshalled))
err = AddBodyKeySanitizer("$.Tag", "Sanitized", "", nil)
require.NoError(t, err)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.Equal(t, "Sanitized", data.Entries[0].ResponseBodyByValue("Tag"))
require.Equal("Sanitized", data.Entries[0].ResponseBodyByValue("Tag"))
}
func TestBodyRegexSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestBodyRegexSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
bodyValue := map[string]string{
"key1": "value1",
}
marshalled, err := json.Marshal(bodyValue)
require.NoError(t, err)
require.NoError(err)
req.Body = io.NopCloser(bytes.NewReader(marshalled))
err = AddBodyRegexSanitizer("Sanitized", "Value", nil)
require.NoError(t, err)
err = AddBodyRegexSanitizer("Sanitized", "https\\:\\/\\/(?<account>[a-z]+)\\.(?:table|blob|queue)\\.core\\.windows\\.net", &RecordingOptions{GroupForReplace: "account"})
require.NoError(t, err)
require.NoError(err)
opts := defaultOptions()
opts.GroupForReplace = "account"
err = AddBodyRegexSanitizer("Sanitized", "https\\:\\/\\/(?<account>[a-z]+)\\.(?:table|blob|queue)\\.core\\.windows\\.net", opts)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.NotContains(t, "storageaccount", data.Entries[0].ResponseBody)
require.NotContains(t, "Value", data.Entries[0].ResponseBody)
require.NotContains("storageaccount", data.Entries[0].ResponseBody)
require.NotContains("Value", data.Entries[0].ResponseBody)
}
func TestRemoveHeaderSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestRemoveHeaderSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
req.Header.Set("FakeStorageLocation", "https://fakeaccount.blob.core.windows.net")
req.Header.Set("ComplexRegexRemove", "https://fakeaccount.table.core.windows.net")
err = AddRemoveHeaderSanitizer([]string{"ComplexRegexRemove", "FakeStorageLocation"}, nil)
require.NoError(t, err)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.NotContains(t, []string{"ComplexRegexRemove", "FakeStorageLocation"}, data.Entries[0].ResponseHeaders)
require.NotContains([]string{"ComplexRegexRemove", "FakeStorageLocation"}, data.Entries[0].ResponseHeaders)
}
func TestContinuationSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestContinuationSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
req.Header.Set("Location", "/posts/2")
bodyValue := map[string]string{
"key1": "value1",
}
marshalled, err := json.Marshal(bodyValue)
require.NoError(t, err)
require.NoError(err)
req.Body = io.NopCloser(bytes.NewReader(marshalled))
err = AddContinuationSanitizer("Location", "Sanitized", true, nil)
require.NoError(t, err)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
req, err = http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err = http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
req.Header.Set("Location", "/posts/3")
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.NotContains(t, "Location", data.Entries[0].ResponseHeaders)
require.NotContains(t, "Location", data.Entries[0].ResponseHeaders)
require.NotContains("Location", data.Entries[0].ResponseHeaders)
require.NotContains("Location", data.Entries[0].ResponseHeaders)
}
func TestGeneralRegexSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestGeneralRegexSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
err = AddGeneralRegexSanitizer("Sanitized", "Value", nil)
require.NoError(t, err)
require.NoError(err)
_, err = client.Do(req)
require.NoError(t, err)
require.NoError(err)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.NotContains(t, "Value", data.Entries[0].ResponseBody)
require.NotContains("Value", data.Entries[0].ResponseBody)
}
func TestOAuthResponseSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestOAuthResponseSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
err = AddOAuthResponseSanitizer(nil)
require.NoError(t, err)
require.NoError(err)
_, err = client.Do(req)
require.NoError(t, err)
require.NoError(err)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
}
func TestUriSubscriptionIdSanitizer(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestUriSubscriptionIdSanitizer() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, "https://management.azure.com/subscriptions/12345678-1234-1234-5678-123456789010/providers/Microsoft.ContainerRegistry/checkNameAvailability?api-version=2019-05-01")
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
err = AddURISubscriptionIDSanitizer("", nil)
require.NoError(t, err)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.Equal(t, "https://management.azure.com/", data.Entries[0].RequestURI)
require.Equal("https://management.azure.com/", data.Entries[0].RequestURI)
}
func TestResetSanitizers(t *testing.T) {
defer reset(t)
func (s *sanitizerTests) TestResetSanitizers() {
require := require.New(s.T())
defer reset(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
err = Start(t, packagePath, nil)
require.NoError(t, err)
err = Start(s.T(), packagePath, nil)
require.NoError(err)
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
client, err := GetHTTPClient(t)
require.NoError(t, err)
client, err := GetHTTPClient(s.T())
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
req.Header.Set(IDHeader, GetRecordingId(t))
req.Header.Set(IDHeader, GetRecordingId(s.T()))
req.Header.Set("FakeStorageLocation", "https://fakeaccount.blob.core.windows.net")
opts := defaultOptions()
opts.TestInstance = s.T()
// Add a sanitizer
err = AddRemoveHeaderSanitizer([]string{"FakeStorageLocation"}, &RecordingOptions{TestInstance: t})
require.NoError(t, err)
err = AddRemoveHeaderSanitizer([]string{"FakeStorageLocation"}, opts)
require.NoError(err)
// Remove all sanitizers
err = ResetProxy(&RecordingOptions{TestInstance: t})
require.NoError(t, err)
err = ResetProxy(opts)
require.NoError(err)
resp, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NoError(err)
require.NotNil(resp)
require.NotNil(t, GetRecordingId(t))
require.NotNil(GetRecordingId(s.T()))
err = Stop(t, nil)
require.NoError(t, err)
err = Stop(s.T(), nil)
require.NoError(err)
// Make sure the file is there
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", s.T().Name()))
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
require.Equal(t, data.Entries[0].RequestHeaders["fakestoragelocation"], "https://fakeaccount.blob.core.windows.net")
require.Equal(data.Entries[0].RequestHeaders["fakestoragelocation"], "https://fakeaccount.blob.core.windows.net")
}
func TestSingleTestSanitizer(t *testing.T) {
func (s *sanitizerTests) TestSingleTestSanitizer() {
require := require.New(s.T())
err := ResetProxy(nil)
require.NoError(t, err)
require.NoError(err)
// The first iteration, add a sanitizer for just that test. The
// second iteration, verify that the sanitizer was not applied.
for i := 0; i < 2; i++ {
t.Run(fmt.Sprintf("%s-%d", t.Name(), i), func(t *testing.T) {
s.T().Run(fmt.Sprintf("%s-%d", s.T().Name(), i), func(t *testing.T) {
err = Start(t, packagePath, nil)
require.NoError(t, err)
require.NoError(err)
if i == 0 {
// The first time we'll set a per-test sanitizer
// Add a sanitizer
err = AddRemoveHeaderSanitizer([]string{"FakeStorageLocation"}, &RecordingOptions{TestInstance: t})
require.NoError(t, err)
opts := defaultOptions()
opts.TestInstance = t
err = AddRemoveHeaderSanitizer([]string{"FakeStorageLocation"}, opts)
require.NoError(err)
}
srvURL := "http://host.docker.internal:8080/uri-sanitizer"
client, err := GetHTTPClient(t)
require.NoError(t, err)
require.NoError(err)
req, err := http.NewRequest("POST", "https://localhost:5001", nil)
require.NoError(t, err)
req, err := http.NewRequest("POST", defaultOptions().baseURL(), nil)
require.NoError(err)
req.Header.Set(UpstreamURIHeader, srvURL)
req.Header.Set(ModeHeader, GetRecordMode())
@ -744,26 +771,26 @@ func TestSingleTestSanitizer(t *testing.T) {
req.Header.Set("FakeStorageLocation", "https://fakeaccount.blob.core.windows.net")
_, err = client.Do(req)
require.NoError(t, err)
require.NoError(err)
err = Stop(t, nil)
require.NoError(t, err)
require.NoError(err)
// Read the file
jsonFile, err := os.Open(fmt.Sprintf("./testdata/recordings/%s.json", t.Name()))
require.NoError(t, err)
require.NoError(err)
defer jsonFile.Close()
var data RecordingFileStruct
byteValue, err := io.ReadAll(jsonFile)
require.NoError(t, err)
require.NoError(err)
err = json.Unmarshal(byteValue, &data)
require.NoError(t, err)
require.NoError(err)
if i == 0 {
require.NotContains(t, data.Entries[0].RequestHeaders, "fakestoragelocation")
require.NotContains(data.Entries[0].RequestHeaders, "fakestoragelocation")
} else {
require.Equal(t, data.Entries[0].RequestHeaders["fakestoragelocation"], "https://fakeaccount.blob.core.windows.net")
require.Equal(data.Entries[0].RequestHeaders["fakestoragelocation"], "https://fakeaccount.blob.core.windows.net")
}
})
}

Просмотреть файл

@ -0,0 +1,446 @@
//go:build go1.18
// +build go1.18
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package recording
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
const ProxyManualStartEnv = "PROXY_MANUAL_START"
type TestProxyInstance struct {
Cmd *exec.Cmd
Options *RecordingOptions
}
func getTestProxyDownloadFile() (string, error) {
switch {
case runtime.GOOS == "windows":
// No ARM binaries for Windows, so return x64
return "test-proxy-standalone-win-x64.zip", nil
case runtime.GOOS == "linux" && runtime.GOARCH == "amd64":
return "test-proxy-standalone-linux-x64.tar.gz", nil
case runtime.GOOS == "linux" && runtime.GOARCH == "arm64":
return "test-proxy-standalone-linux-arm64.tar.gz", nil
case runtime.GOOS == "darwin" && runtime.GOARCH == "amd64":
return "test-proxy-standalone-osx-x64.zip", nil
case runtime.GOOS == "darwin" && runtime.GOARCH == "arm64":
return "test-proxy-standalone-osx-arm64.zip", nil
default:
return "", fmt.Errorf("unsupported OS/Arch combination: %s/%s", runtime.GOOS, runtime.GOARCH)
}
}
// Modified from https://stackoverflow.com/a/24792688
func extractTestProxyZip(src string, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer func() {
if err := r.Close(); err != nil {
panic(err)
}
}()
if err := os.MkdirAll(dest, 0755); err != nil {
return err
}
// Closure to address file descriptors issue with all the deferred .Close() methods
extractAndWriteFile := func(f *zip.File) error {
rc, err := f.Open()
if err != nil {
return err
}
defer func() {
if err := rc.Close(); err != nil {
panic(err)
}
}()
path := filepath.Join(dest, f.Name)
log.Println("Extracting", path)
// Check for ZipSlip (Directory traversal)
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("illegal file path: %s", path)
}
if f.FileInfo().IsDir() {
if err := os.MkdirAll(path, f.Mode()); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Dir(path), f.Mode()); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer func() {
if err := f.Close(); err != nil {
panic(err)
}
}()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
return nil
}
for _, f := range r.File {
err := extractAndWriteFile(f)
if err != nil {
return err
}
}
return nil
}
func extractTestProxyArchive(archivePath string, outputDir string) error {
log.Printf("Extracting %s\n", archivePath)
file, err := os.Open(archivePath)
if err != nil {
return err
}
defer file.Close()
gzipReader, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzipReader.Close()
tarReader := tar.NewReader(gzipReader)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
targetPath := filepath.Join(outputDir, header.Name)
log.Println("Extracting", targetPath)
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(targetPath, 0755); err != nil {
return err
}
case tar.TypeReg:
file, err := os.Create(targetPath)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(file, tarReader); err != nil {
return err
}
default:
log.Printf("Unable to extract type %c in file %s\n", header.Typeflag, header.Name)
}
}
return nil
}
func installTestProxy(archivePath string, outputDir string, proxyPath string) error {
var err error
if strings.HasSuffix(archivePath, ".zip") {
err = extractTestProxyZip(archivePath, outputDir)
} else {
err = extractTestProxyArchive(archivePath, outputDir)
}
if err != nil {
return err
}
err = os.Chmod(proxyPath, 0755)
if err != nil {
return err
}
err = os.Remove(archivePath)
if err != nil {
return err
}
return nil
}
func restoreRecordings(proxyPath string, pathToRecordings string) error {
if pathToRecordings == "" {
return nil
}
absAssetLocation, _, err := getAssetsConfigLocation(pathToRecordings)
if err != nil {
return err
}
log.Printf("Running test proxy command: %s restore -a %s\n", proxyPath, absAssetLocation)
cmd := exec.Command(proxyPath, "restore", "-a", absAssetLocation)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return err
}
return cmd.Wait()
}
func ensureTestProxyInstalled(proxyVersion string, proxyPath string, proxyDir string, pathToRecordings string) error {
lockFile := filepath.Join(os.TempDir(), "test-proxy-install.lock")
log.Printf("Waiting to acquire test proxy install lock %s\n", lockFile)
maxTries := 600 // Wait 1 minute
var i int
for i = 0; i < maxTries; i++ {
lock, err := os.OpenFile(lockFile, os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
time.Sleep(100 * time.Millisecond)
continue
}
// NOTE: the lockfile will not be removed on ctrl-c during download.
// Go test seems to send an os.Interrupt signal on test setup completion, so if we
// call os.Exit(1) on ctrl-c the tests will never run. If we don't call os.Exit(1),
// the tests cannot be canceled.
// Therefore, if ctrl-c is pressed during download, the user will have to manually
// remove the lockfile in order to get the tests running again.
defer func() {
lock.Close()
os.Remove(lockFile)
}()
break
}
if i >= maxTries {
return fmt.Errorf("timed out waiting to acquire test proxy install lock. Ensure %s does not exist", lockFile)
}
cmd := exec.Command(proxyPath, "--version")
out, err := cmd.Output()
if err != nil {
log.Printf("Test proxy not detected at %s, downloading...\n", proxyPath)
} else {
// TODO: fix proxy CLI tool versioning output to match the actual version we download
installedVersion := "1.0.0-dev." + strings.TrimSpace(string(out))
if installedVersion == proxyVersion {
log.Printf("Test proxy version %s already installed\n", proxyVersion)
return restoreRecordings(proxyPath, pathToRecordings)
} else {
log.Printf("Test proxy version %s does not match required version %s\n",
installedVersion, proxyVersion)
}
}
proxyFile, err := getTestProxyDownloadFile()
if err != nil {
return err
}
proxyDownloadPath := filepath.Join(proxyDir, proxyFile)
archive, err := os.Create(proxyDownloadPath)
if err != nil {
return err
}
log.Printf("Downloading test proxy version %s to %s for %s/%s\n",
proxyVersion, proxyPath, runtime.GOOS, runtime.GOARCH)
proxyUrl := fmt.Sprintf("https://github.com/Azure/azure-sdk-tools/releases/download/Azure.Sdk.Tools.TestProxy_%s/%s",
proxyVersion, proxyFile)
resp, err := http.Get(proxyUrl)
if err != nil {
return err
}
_, err = io.Copy(archive, resp.Body)
if err != nil {
return err
}
err = resp.Body.Close()
if err != nil {
return err
}
err = archive.Close()
if err != nil {
return err
}
err = installTestProxy(proxyDownloadPath, proxyDir, proxyPath)
if err != nil {
return err
}
return restoreRecordings(proxyPath, pathToRecordings)
}
func getProxyLog() (*os.File, error) {
rand.Seed(time.Now().UnixNano())
const letters = "abcdefghijklmnopqrstuvwxyz"
suffix := make([]byte, 6)
for i := range suffix {
suffix[i] = letters[rand.Intn(len(letters))]
}
proxyLogName := fmt.Sprintf("test-proxy.log.%s", suffix)
proxyLog, err := os.Create(filepath.Join(os.TempDir(), proxyLogName))
if err != nil {
return nil, err
}
return proxyLog, nil
}
func getProxyVersion(gitRoot string) (string, error) {
proxyVersionConfig := filepath.Join(gitRoot, "eng/common/testproxy/target_version.txt")
version, err := ioutil.ReadFile(proxyVersionConfig)
if err != nil {
return "", err
}
proxyVersion := strings.TrimSpace(string(version))
return proxyVersion, nil
}
func setTestProxyEnv(gitRoot string) {
devCertPath := filepath.Join(gitRoot, "eng/common/testproxy/dotnet-devcert.pfx")
os.Setenv("ASPNETCORE_Kestrel__Certificates__Default__Path", devCertPath)
os.Setenv("ASPNETCORE_Kestrel__Certificates__Default__Password", "password")
}
func waitForProxyStart(cmd *exec.Cmd, options *RecordingOptions) (*TestProxyInstance, error) {
maxTries := 50
// Extend sleep time in devops pipeline, proxy takes longer to start up
if os.Getenv("SYSTEM_TEAMPROJECTID") != "" {
maxTries = 200
}
log.Printf("Started test proxy instance (PID %d) on %s\n", cmd.Process.Pid, options.baseURL())
client, _ := GetHTTPClient(nil)
client.Timeout = 1 * time.Second
log.Printf("Waiting up to %d seconds for test-proxy server to respond...\n", (maxTries / 10))
var i int
for i = 0; i < maxTries; i++ {
uri := fmt.Sprintf("https://localhost:%d/Admin/IsAlive", options.ProxyPort)
req, _ := http.NewRequest("GET", uri, nil)
req.Close = true
resp, err := client.Do(req)
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
if err != nil {
time.Sleep(100 * time.Millisecond)
continue
}
return &TestProxyInstance{Cmd: cmd, Options: options}, nil
}
return nil, fmt.Errorf("test proxy server did not become available in the allotted time")
}
func StartTestProxy(pathToRecordings string, options *RecordingOptions) (*TestProxyInstance, error) {
manualStart := strings.ToLower(os.Getenv(ProxyManualStartEnv))
if manualStart == "true" {
log.Printf("%s env variable is set to true, not starting test proxy...\n", ProxyManualStartEnv)
return nil, nil
}
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
gitRoot, err := getGitRoot(cwd)
if err != nil {
return nil, err
}
proxyVersion, err := getProxyVersion(gitRoot)
if err != nil {
return nil, err
}
proxyDir := filepath.Join(gitRoot, ".proxy")
if err := os.MkdirAll(proxyDir, 0755); err != nil {
return nil, err
}
proxyPath := filepath.Join(proxyDir, "Azure.Sdk.Tools.TestProxy")
if runtime.GOOS == "windows" {
proxyPath += ".exe"
}
err = ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir, pathToRecordings)
if err != nil {
return nil, err
}
proxyLog, err := getProxyLog()
if err != nil {
return nil, err
}
defer proxyLog.Close()
setTestProxyEnv(gitRoot)
if options == nil {
options = defaultOptions()
}
log.Printf("Running test proxy command: %s start --storage-location %s -- --urls=%s\n",
proxyPath, gitRoot, options.baseURL())
log.Printf("Test proxy log location: %s\n", proxyLog.Name())
cmd := exec.Command(
proxyPath, "start", "--storage-location", gitRoot, "--", "--urls="+options.baseURL())
cmd.Stdout = proxyLog
cmd.Stderr = proxyLog
if err := cmd.Start(); err != nil {
return nil, err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
return waitForProxyStart(cmd, options)
}
// NOTE: The process will be killed if the user hits ctrl-c mid-way through tests, as go will
// kill child processes when the main process does not exit cleanly. No os.Interrupt handlers
// need to be added after starting the proxy server in tests.
func StopTestProxy(proxyInstance *TestProxyInstance) error {
if proxyInstance == nil || proxyInstance.Cmd == nil || proxyInstance.Cmd.Process == nil {
return nil
}
log.Printf("Stopping test proxy instance (PID %d) on %s\n", proxyInstance.Cmd.Process.Pid, proxyInstance.Options.baseURL())
err := proxyInstance.Cmd.Process.Kill()
if err != nil {
return err
}
return nil
}

Просмотреть файл

@ -0,0 +1,84 @@
//go:build go1.18
// +build go1.18
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package recording
import (
"archive/zip"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type serverTests struct {
suite.Suite
}
func TestServer(t *testing.T) {
suite.Run(t, new(serverTests))
}
func (s *serverTests) SetupSuite() {
// Ignore manual start in pipeline tests, we always want to exercise install
os.Setenv(ProxyManualStartEnv, "false")
}
func (s *serverTests) TestProxyDownloadFile() {
file, err := getTestProxyDownloadFile()
require.NoError(s.T(), err)
require.NotEmpty(s.T(), file)
}
func (s *serverTests) TestExtractTestProxyZip() {
zipFile, err := os.CreateTemp("", "test-extract-*.zip")
require.NoError(s.T(), err)
defer zipFile.Close()
// Create a new zip archive
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
}
func (s *serverTests) TestEnsureTestProxyInstalled() {
cwd, err := os.Getwd()
require.NoError(s.T(), err)
gitRoot, err := getGitRoot(cwd)
require.NoError(s.T(), err)
proxyDir := filepath.Join(os.TempDir(), ".proxy")
proxyVersion, err := getProxyVersion(gitRoot)
require.NoError(s.T(), err)
err = os.RemoveAll(proxyDir)
require.NoError(s.T(), err)
err = os.MkdirAll(proxyDir, 0755)
require.NoError(s.T(), err)
proxyPath := filepath.Join(proxyDir, "Azure.Sdk.Tools.TestProxy")
if runtime.GOOS == "windows" {
proxyPath += ".exe"
}
// Test download proxy
err = ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir, "")
require.NoError(s.T(), err)
stat1, err := os.Stat(proxyPath)
require.NoError(s.T(), err)
// Test cached proxy
err = ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir, "")
require.NoError(s.T(), err)
stat2, err := os.Stat(proxyPath)
require.NoError(s.T(), err)
require.Equal(s.T(), stat1.ModTime(), stat2.ModTime(), "Expected proxy download to be cached")
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"Entries": [],
"Variables": {
"randSeed": "1676365801"
"randSeed": "1689722394"
}
}

Просмотреть файл

@ -1,36 +0,0 @@
{
"Entries": [
{
"RequestUri": "https://bing.com/",
"RequestMethod": "POST",
"RequestHeaders": {
":method": "POST",
"Accept-Encoding": "gzip",
"Content-Length": "0",
"User-Agent": "Go-http-client/2.0"
},
"RequestBody": null,
"StatusCode": 301,
"ResponseHeaders": {
"Accept-CH": "Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version",
"Cache-Control": "private",
"Content-Encoding": "gzip",
"Content-Length": "2943",
"Content-Type": "text/html; charset=utf-8",
"Date": "Thu, 17 Feb 2022 20:30:07 GMT",
"Location": "https://www.bing.com/?toWww=1\u0026redig=81BB7CD9B29C416FB30ABD795D00AAF6\u0026toWww=1\u0026redig=CE7B5383D95F46FF947B2BC37CB68D6E\u0026toWww=1\u0026redig=CB8EFFEA2DCD459EB1E820873F6A0ED5\u0026toWww=1\u0026redig=66E6D5B1A52A42DAA48FCB45A1F58C7F\u0026toWww=1\u0026redig=1C900977865E48A3AE8A940E067B4360\u0026toWww=1\u0026redig=6ABF54D5B5E7420B93AB52A135102BB3\u0026toWww=1\u0026redig=D8EEC4D6F219451EA57C61AC8BBD3750\u0026toWww=1\u0026redig=CEEFBEA9597847EA9F7E199BDD5B628B\u0026toWww=1\u0026redig=7365DCE371C14626AD7A59CE7B01A342\u0026toWww=1\u0026redig=857F8622D57C4601AA117E048C2AB88A\u0026toWww=1\u0026redig=68DA9FADFEED465BA3E7D3380248665F\u0026toWww=1\u0026redig=ED3C1969DDB94E73A1BA017644325800\u0026toWww=1\u0026redig=24AEF3549F1945F9BD48015E6E753B1E\u0026toWww=1\u0026redig=9D107AB46F5C495B8667EE4808973116\u0026toWww=1\u0026redig=644D912599CE476DAFA9717B97E5EFD5\u0026toWww=1\u0026redig=63C86EAFEBDC465BBA15E04318FB4C22\u0026toWww=1\u0026redig=147FEF40AA4F423B9247902EB4D1D6C8\u0026toWww=1\u0026redig=8787BF0DE47248B5BA2B0CDA17B7BBEA\u0026toWww=1\u0026redig=782D6A378BE042CA8CEE385EA0A7ADF7\u0026toWww=1\u0026redig=A11ABEEA72D14BCE9FAA50AC3419CE6C\u0026toWww=1\u0026redig=4F151488BDF54D108D1B19A1F7D56C20\u0026toWww=1\u0026redig=E9CBC0E274224D859522A7D6E4D2EC80\u0026toWww=1\u0026redig=49622F30DE1F443BB2F568DE2E56766E\u0026toWww=1\u0026redig=8B043DABBA1A40D3B207456B4D103699\u0026toWww=1\u0026redig=5A095C4F102E40E8A6111644C70EFF5C\u0026toWww=1\u0026redig=BA559D266FF6435C98D9AE721A815C10\u0026toWww=1\u0026redig=314FCF35913F4F41B5A34499FBF36043\u0026toWww=1\u0026redig=FE196F2DE37247CAB08E84DC1BEE0AD1\u0026toWww=1\u0026redig=6A0FCED8A5FD4E26AB50BA70DA48CE18\u0026toWww=1\u0026redig=51EDFBCD545344AB83D2C4EE79F9B95C\u0026toWww=1\u0026redig=9C416419D8464C0B9DF4D597D2C93EC1\u0026toWww=1\u0026redig=EDF79189D66A4787957D8B2DFE6C639C\u0026toWww=1\u0026redig=0AB731BB5CD7447E841E3742540A92CC\u0026toWww=1\u0026redig=0CB28E343C654DEFAE62619A6759F149\u0026toWww=1\u0026redig=3299895DFC7D4BDFBF995FA7BF319831\u0026toWww=1\u0026redig=1DFD1169D5A44FA7B7C20F06149550D2\u0026toWww=1\u0026redig=3255FC6416DC45CDA44F3254FF8030AA\u0026toWww=1\u0026redig=1CEA595A81C240178409321330F67D4F\u0026toWww=1\u0026redig=6EFE965CFE0846D885553D444E36096C\u0026toWww=1\u0026redig=295135DAA5504F1095967A3392D68BFB\u0026toWww=1\u0026redig=6A141735E0704BD1A1D78AA09CF8B51B\u0026toWww=1\u0026redig=58C30338DCD24E84A9EBDF52FEA0A478\u0026toWww=1\u0026redig=DE5E02CD46A84B44A30A4EF8B2EBA4FF\u0026toWww=1\u0026redig=D940173748BD4AFCB9FD0D0CD6ACB637\u0026toWww=1\u0026redig=358C78D6DE7F4778A2B32756DED73B0E\u0026toWww=1\u0026redig=04B2279FC9DF43088287B9113903001C\u0026toWww=1\u0026redig=80076EF3515B43E58FC917D26035E114\u0026toWww=1\u0026redig=E8AECD73A97C44B089420D07DA1253D5\u0026toWww=1\u0026redig=4A36194C4D1148178DD4B1E5BF2DDE23\u0026toWww=1\u0026redig=BEFE121F45A8466BBD4C76D8E318744B\u0026toWww=1\u0026redig=1486C7748639437E80896BB1A36AA22B",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"Vary": "Accept-Encoding",
"X-Cache": "CONFIG_NOCACHE",
"X-MSEdge-Ref": "Ref A: 624295BD473146C09FA61B70DA6E8A0A Ref B: ASHEDGE1221 Ref C: 2022-02-17T20:30:07Z",
"X-SNR-Routing": "1"
},
"ResponseBody": [
"\u003Chtml\u003E\u003Chead\u003E\u003Ctitle\u003EObject moved\u003C/title\u003E\u003C/head\u003E\u003Cbody\u003E\r\n",
"\u003Ch2\u003EObject moved to \u003Ca href=\u0022https://www.bing.com:443/?toWww=1\u0026amp;redig=81BB7CD9B29C416FB30ABD795D00AAF6\u0026amp;toWww=1\u0026amp;redig=CE7B5383D95F46FF947B2BC37CB68D6E\u0026amp;toWww=1\u0026amp;redig=CB8EFFEA2DCD459EB1E820873F6A0ED5\u0026amp;toWww=1\u0026amp;redig=66E6D5B1A52A42DAA48FCB45A1F58C7F\u0026amp;toWww=1\u0026amp;redig=1C900977865E48A3AE8A940E067B4360\u0026amp;toWww=1\u0026amp;redig=6ABF54D5B5E7420B93AB52A135102BB3\u0026amp;toWww=1\u0026amp;redig=D8EEC4D6F219451EA57C61AC8BBD3750\u0026amp;toWww=1\u0026amp;redig=CEEFBEA9597847EA9F7E199BDD5B628B\u0026amp;toWww=1\u0026amp;redig=7365DCE371C14626AD7A59CE7B01A342\u0026amp;toWww=1\u0026amp;redig=857F8622D57C4601AA117E048C2AB88A\u0026amp;toWww=1\u0026amp;redig=68DA9FADFEED465BA3E7D3380248665F\u0026amp;toWww=1\u0026amp;redig=ED3C1969DDB94E73A1BA017644325800\u0026amp;toWww=1\u0026amp;redig=24AEF3549F1945F9BD48015E6E753B1E\u0026amp;toWww=1\u0026amp;redig=9D107AB46F5C495B8667EE4808973116\u0026amp;toWww=1\u0026amp;redig=644D912599CE476DAFA9717B97E5EFD5\u0026amp;toWww=1\u0026amp;redig=63C86EAFEBDC465BBA15E04318FB4C22\u0026amp;toWww=1\u0026amp;redig=147FEF40AA4F423B9247902EB4D1D6C8\u0026amp;toWww=1\u0026amp;redig=8787BF0DE47248B5BA2B0CDA17B7BBEA\u0026amp;toWww=1\u0026amp;redig=782D6A378BE042CA8CEE385EA0A7ADF7\u0026amp;toWww=1\u0026amp;redig=A11ABEEA72D14BCE9FAA50AC3419CE6C\u0026amp;toWww=1\u0026amp;redig=4F151488BDF54D108D1B19A1F7D56C20\u0026amp;toWww=1\u0026amp;redig=E9CBC0E274224D859522A7D6E4D2EC80\u0026amp;toWww=1\u0026amp;redig=49622F30DE1F443BB2F568DE2E56766E\u0026amp;toWww=1\u0026amp;redig=8B043DABBA1A40D3B207456B4D103699\u0026amp;toWww=1\u0026amp;redig=5A095C4F102E40E8A6111644C70EFF5C\u0026amp;toWww=1\u0026amp;redig=BA559D266FF6435C98D9AE721A815C10\u0026amp;toWww=1\u0026amp;redig=314FCF35913F4F41B5A34499FBF36043\u0026amp;toWww=1\u0026amp;redig=FE196F2DE37247CAB08E84DC1BEE0AD1\u0026amp;toWww=1\u0026amp;redig=6A0FCED8A5FD4E26AB50BA70DA48CE18\u0026amp;toWww=1\u0026amp;redig=51EDFBCD545344AB83D2C4EE79F9B95C\u0026amp;toWww=1\u0026amp;redig=9C416419D8464C0B9DF4D597D2C93EC1\u0026amp;toWww=1\u0026amp;redig=EDF79189D66A4787957D8B2DFE6C639C\u0026amp;toWww=1\u0026amp;redig=0AB731BB5CD7447E841E3742540A92CC\u0026amp;toWww=1\u0026amp;redig=0CB28E343C654DEFAE62619A6759F149\u0026amp;toWww=1\u0026amp;redig=3299895DFC7D4BDFBF995FA7BF319831\u0026amp;toWww=1\u0026amp;redig=1DFD1169D5A44FA7B7C20F06149550D2\u0026amp;toWww=1\u0026amp;redig=3255FC6416DC45CDA44F3254FF8030AA\u0026amp;toWww=1\u0026amp;redig=1CEA595A81C240178409321330F67D4F\u0026amp;toWww=1\u0026amp;redig=6EFE965CFE0846D885553D444E36096C\u0026amp;toWww=1\u0026amp;redig=295135DAA5504F1095967A3392D68BFB\u0026amp;toWww=1\u0026amp;redig=6A141735E0704BD1A1D78AA09CF8B51B\u0026amp;toWww=1\u0026amp;redig=58C30338DCD24E84A9EBDF52FEA0A478\u0026amp;toWww=1\u0026amp;redig=DE5E02CD46A84B44A30A4EF8B2EBA4FF\u0026amp;toWww=1\u0026amp;redig=D940173748BD4AFCB9FD0D0CD6ACB637\u0026amp;toWww=1\u0026amp;redig=358C78D6DE7F4778A2B32756DED73B0E\u0026amp;toWww=1\u0026amp;redig=04B2279FC9DF43088287B9113903001C\u0026amp;toWww=1\u0026amp;redig=80076EF3515B43E58FC917D26035E114\u0026amp;toWww=1\u0026amp;redig=E8AECD73A97C44B089420D07DA1253D5\u0026amp;toWww=1\u0026amp;redig=4A36194C4D1148178DD4B1E5BF2DDE23\u0026amp;toWww=1\u0026amp;redig=BEFE121F45A8466BBD4C76D8E318744B\u0026amp;toWww=1\u0026amp;redig=1486C7748639437E80896BB1A36AA22B\u0022\u003Ehere\u003C/a\u003E.\u003C/h2\u003E\r\n",
"\u003C/body\u003E\u003C/html\u003E\r\n"
]
}
],
"Variables": {}
}

Просмотреть файл

@ -1,36 +0,0 @@
{
"Entries": [
{
"RequestUri": "https://bing.com/",
"RequestMethod": "POST",
"RequestHeaders": {
":method": "POST",
"Accept-Encoding": "gzip",
"Content-Length": "0",
"User-Agent": "Go-http-client/2.0"
},
"RequestBody": null,
"StatusCode": 301,
"ResponseHeaders": {
"Accept-CH": "Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version",
"Cache-Control": "private",
"Content-Encoding": "gzip",
"Content-Length": "2943",
"Content-Type": "text/html; charset=utf-8",
"Date": "Thu, 17 Feb 2022 20:30:13 GMT",
"Location": "https://www.bing.com/?toWww=1\u0026redig=2BD048EA61C843BF858ECF8BA81F527E\u0026toWww=1\u0026redig=223C42F9DD15458584170A0103277F52\u0026toWww=1\u0026redig=8958623586264C269F2FA43A2E235593\u0026toWww=1\u0026redig=5B53A319EB244E8FACB75530BB793E9C\u0026toWww=1\u0026redig=E1B70FC75AF44ED382ABF5FE066867D5\u0026toWww=1\u0026redig=6C0D51C499254EE6A43250D8831DB8BF\u0026toWww=1\u0026redig=97D61E64FF1E463289FE355B9AE04D72\u0026toWww=1\u0026redig=929B3DF5252046D6A431309997E84853\u0026toWww=1\u0026redig=077B4C1EBFD94A20B3CC849876F63F13\u0026toWww=1\u0026redig=F64DF7F462BA4EF29CEF1EBE25399D12\u0026toWww=1\u0026redig=9B4C7D8B003D4E9EA580909984C9DDC5\u0026toWww=1\u0026redig=45689429E4044FC0A0FB1A715361B24A\u0026toWww=1\u0026redig=F4FE54C0951A440291601D6D1FA02D27\u0026toWww=1\u0026redig=BE8DBEC371AA48478038EC62AFE7D983\u0026toWww=1\u0026redig=E5A87AE1F00C499CBEEFAFED1F60DDED\u0026toWww=1\u0026redig=63F14CED1050430D8FB08507682A3990\u0026toWww=1\u0026redig=0BEB5406EB4D4C10BF4AD78B73D7360E\u0026toWww=1\u0026redig=E876ED4D917141A296E8321EF3AD0243\u0026toWww=1\u0026redig=D74AFD9C9D92427ABBC9D004DDE2F109\u0026toWww=1\u0026redig=3AF9AF2FC4FB4CAD94F473660F93E6BF\u0026toWww=1\u0026redig=1DFC418AF4E54E1EB6A11FCAD4635F4D\u0026toWww=1\u0026redig=6F4D8F465203478AA4A3A4B368816ACF\u0026toWww=1\u0026redig=E880D38280C648BEA2F29BF06C0F04DB\u0026toWww=1\u0026redig=56DDE4FDEE17496E9A8452AB09683574\u0026toWww=1\u0026redig=F8ACAFD5D67444E8812406E0AD13F86D\u0026toWww=1\u0026redig=5951262928414B5D96442BA5A0297B30\u0026toWww=1\u0026redig=F64B9013A94C46A8A5E672D25855E4B7\u0026toWww=1\u0026redig=FCA0EDE51D0046BAA71DCDACD20D8AA3\u0026toWww=1\u0026redig=EA7A8646EE9E41DA862E10F0E439325E\u0026toWww=1\u0026redig=1145E8376D544D70A4C3235B527E09E9\u0026toWww=1\u0026redig=22BDF6CCBEAC4B5FB49E552E5D734064\u0026toWww=1\u0026redig=65CA5ACF556748538C517B04C7599406\u0026toWww=1\u0026redig=EAE28C3A9DFC466EADC945568650E779\u0026toWww=1\u0026redig=95C5F5680BC844A385309F1DF5DA1A2E\u0026toWww=1\u0026redig=3C80C0D77CA14D69873147464CF7D8D0\u0026toWww=1\u0026redig=F9602FD488384082817470B482A7B6EB\u0026toWww=1\u0026redig=8085AB612674463F84146DBDC4B88417\u0026toWww=1\u0026redig=6D492C540C11437ABA9AF98CE157884D\u0026toWww=1\u0026redig=0CC0AAD83CC142D18A2FB38E756FB704\u0026toWww=1\u0026redig=AB2C0D3A5219429892E66BB676B32729\u0026toWww=1\u0026redig=752046EDE8AF48A19C8F7B56AB76F5D5\u0026toWww=1\u0026redig=358DFA1FDBDC4CF1A67AF4B51BFEEA16\u0026toWww=1\u0026redig=3DACA2E4587B47DA99F62E335314C9DE\u0026toWww=1\u0026redig=06CFC2438A794A9F8D1DE56B0CC2B332\u0026toWww=1\u0026redig=C8BB307EBF4E4B5393386023731897EA\u0026toWww=1\u0026redig=61638197A754402790E5EB9DB6F8CF57\u0026toWww=1\u0026redig=45027964670E458B95CD8902A6EB1278\u0026toWww=1\u0026redig=5E472A5E625D40EF9F108A1CB2870CFE\u0026toWww=1\u0026redig=0657C8E4009044559FB23B6D084C554B\u0026toWww=1\u0026redig=B960DBDF53F44E019A648FD713930E9A\u0026toWww=1\u0026redig=5D00DDCDB0754DB38306BE1CF3B84E92",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"Vary": "Accept-Encoding",
"X-Cache": "CONFIG_NOCACHE",
"X-MSEdge-Ref": "Ref A: 3F61F126A212460BB79BDF9FA51CB01D Ref B: ASHEDGE1221 Ref C: 2022-02-17T20:30:13Z",
"X-SNR-Routing": "1"
},
"ResponseBody": [
"\u003Chtml\u003E\u003Chead\u003E\u003Ctitle\u003EObject moved\u003C/title\u003E\u003C/head\u003E\u003Cbody\u003E\r\n",
"\u003Ch2\u003EObject moved to \u003Ca href=\u0022https://www.bing.com:443/?toWww=1\u0026amp;redig=2BD048EA61C843BF858ECF8BA81F527E\u0026amp;toWww=1\u0026amp;redig=223C42F9DD15458584170A0103277F52\u0026amp;toWww=1\u0026amp;redig=8958623586264C269F2FA43A2E235593\u0026amp;toWww=1\u0026amp;redig=5B53A319EB244E8FACB75530BB793E9C\u0026amp;toWww=1\u0026amp;redig=E1B70FC75AF44ED382ABF5FE066867D5\u0026amp;toWww=1\u0026amp;redig=6C0D51C499254EE6A43250D8831DB8BF\u0026amp;toWww=1\u0026amp;redig=97D61E64FF1E463289FE355B9AE04D72\u0026amp;toWww=1\u0026amp;redig=929B3DF5252046D6A431309997E84853\u0026amp;toWww=1\u0026amp;redig=077B4C1EBFD94A20B3CC849876F63F13\u0026amp;toWww=1\u0026amp;redig=F64DF7F462BA4EF29CEF1EBE25399D12\u0026amp;toWww=1\u0026amp;redig=9B4C7D8B003D4E9EA580909984C9DDC5\u0026amp;toWww=1\u0026amp;redig=45689429E4044FC0A0FB1A715361B24A\u0026amp;toWww=1\u0026amp;redig=F4FE54C0951A440291601D6D1FA02D27\u0026amp;toWww=1\u0026amp;redig=BE8DBEC371AA48478038EC62AFE7D983\u0026amp;toWww=1\u0026amp;redig=E5A87AE1F00C499CBEEFAFED1F60DDED\u0026amp;toWww=1\u0026amp;redig=63F14CED1050430D8FB08507682A3990\u0026amp;toWww=1\u0026amp;redig=0BEB5406EB4D4C10BF4AD78B73D7360E\u0026amp;toWww=1\u0026amp;redig=E876ED4D917141A296E8321EF3AD0243\u0026amp;toWww=1\u0026amp;redig=D74AFD9C9D92427ABBC9D004DDE2F109\u0026amp;toWww=1\u0026amp;redig=3AF9AF2FC4FB4CAD94F473660F93E6BF\u0026amp;toWww=1\u0026amp;redig=1DFC418AF4E54E1EB6A11FCAD4635F4D\u0026amp;toWww=1\u0026amp;redig=6F4D8F465203478AA4A3A4B368816ACF\u0026amp;toWww=1\u0026amp;redig=E880D38280C648BEA2F29BF06C0F04DB\u0026amp;toWww=1\u0026amp;redig=56DDE4FDEE17496E9A8452AB09683574\u0026amp;toWww=1\u0026amp;redig=F8ACAFD5D67444E8812406E0AD13F86D\u0026amp;toWww=1\u0026amp;redig=5951262928414B5D96442BA5A0297B30\u0026amp;toWww=1\u0026amp;redig=F64B9013A94C46A8A5E672D25855E4B7\u0026amp;toWww=1\u0026amp;redig=FCA0EDE51D0046BAA71DCDACD20D8AA3\u0026amp;toWww=1\u0026amp;redig=EA7A8646EE9E41DA862E10F0E439325E\u0026amp;toWww=1\u0026amp;redig=1145E8376D544D70A4C3235B527E09E9\u0026amp;toWww=1\u0026amp;redig=22BDF6CCBEAC4B5FB49E552E5D734064\u0026amp;toWww=1\u0026amp;redig=65CA5ACF556748538C517B04C7599406\u0026amp;toWww=1\u0026amp;redig=EAE28C3A9DFC466EADC945568650E779\u0026amp;toWww=1\u0026amp;redig=95C5F5680BC844A385309F1DF5DA1A2E\u0026amp;toWww=1\u0026amp;redig=3C80C0D77CA14D69873147464CF7D8D0\u0026amp;toWww=1\u0026amp;redig=F9602FD488384082817470B482A7B6EB\u0026amp;toWww=1\u0026amp;redig=8085AB612674463F84146DBDC4B88417\u0026amp;toWww=1\u0026amp;redig=6D492C540C11437ABA9AF98CE157884D\u0026amp;toWww=1\u0026amp;redig=0CC0AAD83CC142D18A2FB38E756FB704\u0026amp;toWww=1\u0026amp;redig=AB2C0D3A5219429892E66BB676B32729\u0026amp;toWww=1\u0026amp;redig=752046EDE8AF48A19C8F7B56AB76F5D5\u0026amp;toWww=1\u0026amp;redig=358DFA1FDBDC4CF1A67AF4B51BFEEA16\u0026amp;toWww=1\u0026amp;redig=3DACA2E4587B47DA99F62E335314C9DE\u0026amp;toWww=1\u0026amp;redig=06CFC2438A794A9F8D1DE56B0CC2B332\u0026amp;toWww=1\u0026amp;redig=C8BB307EBF4E4B5393386023731897EA\u0026amp;toWww=1\u0026amp;redig=61638197A754402790E5EB9DB6F8CF57\u0026amp;toWww=1\u0026amp;redig=45027964670E458B95CD8902A6EB1278\u0026amp;toWww=1\u0026amp;redig=5E472A5E625D40EF9F108A1CB2870CFE\u0026amp;toWww=1\u0026amp;redig=0657C8E4009044559FB23B6D084C554B\u0026amp;toWww=1\u0026amp;redig=B960DBDF53F44E019A648FD713930E9A\u0026amp;toWww=1\u0026amp;redig=5D00DDCDB0754DB38306BE1CF3B84E92\u0022\u003Ehere\u003C/a\u003E.\u003C/h2\u003E\r\n",
"\u003C/body\u003E\u003C/html\u003E\r\n"
]
}
],
"Variables": {}
}