From 475add0421b4ad474e5cf3d96123b0717abdbf1c Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Mon, 10 Jul 2023 22:42:46 -0400 Subject: [PATCH 1/8] Auto-install and run standalone test proxy server per test package --- .gitignore | 3 + sdk/internal/CHANGELOG.md | 2 + sdk/internal/recording/recording.go | 13 +- sdk/internal/recording/server.go | 329 ++++++++++++++++++ .../keyvault/azadmin/backup/utils_test.go | 2 +- .../keyvault/azadmin/rbac/utils_test.go | 2 +- .../keyvault/azadmin/settings/utils_test.go | 2 +- 7 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 sdk/internal/recording/server.go diff --git a/.gitignore b/.gitignore index 61f01a7c59cf..d33a9a16046a 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ vendor/ # Default Test Proxy Assets restore directory .assets + +# Default Test Proxy tools install directory +.proxy diff --git a/sdk/internal/CHANGELOG.md b/sdk/internal/CHANGELOG.md index a23d25e5ea77..76b5774b27df 100644 --- a/sdk/internal/CHANGELOG.md +++ b/sdk/internal/CHANGELOG.md @@ -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 diff --git a/sdk/internal/recording/recording.go b/sdk/internal/recording/recording.go index b9b2a3e0ca4a..2d123b9e6dd2 100644 --- a/sdk/internal/recording/recording.go +++ b/sdk/internal/recording/recording.go @@ -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" } @@ -667,7 +673,8 @@ 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 +// Start optionally installs and starts a test proxy instance +// and tells the test proxy instance to begin accepting requests for a given test func Start(t *testing.T, pathToRecordings string, options *RecordingOptions) error { if options == nil { options = defaultOptions() @@ -940,7 +947,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 { diff --git a/sdk/internal/recording/server.go b/sdk/internal/recording/server.go new file mode 100644 index 000000000000..154768ab4661 --- /dev/null +++ b/sdk/internal/recording/server.go @@ -0,0 +1,329 @@ +//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" + "strings" + "runtime" + "time" +) + +func getTestProxyDownloadFile() (string, error) { + if runtime.GOOS == "windows" { + return "test-proxy-standalone-win-x64.zip", nil + } + + switch { + 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) + } +} + +func extractTestProxyZip(archivePath string, outputDir string) error { + // Open the zip file + r, err := zip.OpenReader(archivePath) + if err != nil { + panic(err) + } + defer r.Close() + + for _, f := range r.File { + targetPath := filepath.Join(outputDir, f.Name) + + log.Println("Extracting", targetPath) + + if f.FileInfo().IsDir() { + os.MkdirAll(targetPath, f.Mode()) + continue + } + + file, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer file.Close() + + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + if _, err = io.Copy(file, rc); 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 extractTestProxy(archivePath string, outputDir string) error { + if strings.HasSuffix(archivePath, ".zip") { + return extractTestProxyZip(archivePath, outputDir) + } else { + return extractTestProxyArchive(archivePath, outputDir) + } +} + +func ensureTestProxyInstalled(proxyVersion string, proxyPath string, proxyDir string) error { + lockFile := filepath.Join(os.TempDir(), "test-proxy-install.lock") + 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() { + os.Remove(lockFile) + lock.Close() + }() + + 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 nil + } 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 + } + defer archive.Close() + + 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 + } + defer resp.Body.Close() + + _, err = io.Copy(archive, resp.Body) + if err != nil { + return err + } + + err = extractTestProxy(proxyDownloadPath, proxyDir) + if err != nil { + return err + } + err = os.Chmod(proxyPath, 0755) + if err != nil { + return err + } + err = os.Remove(proxyDownloadPath) + if err != nil { + return err + } + + return nil +} + +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("testproxy.log.%s", suffix) + proxyLog, err := os.Create(filepath.Join(os.TempDir(), proxyLogName)) + if err != nil { + return nil, err + } + return proxyLog, nil +} + +func StartTestProxyInstance(options *RecordingOptions) (*exec.Cmd, error) { + manualStart := strings.ToLower(os.Getenv("PROXY_MANUAL_START")) + if manualStart == "true" { + log.Println("PROXY_MANUAL_START env variable is set to true, not starting test proxy...") + return nil, nil + } + + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + gitRoot, err := getGitRoot(cwd) + if err != nil { + return nil, err + } + proxyVersionConfig := filepath.Join(gitRoot, "eng/common/testproxy/target_version.txt") + version, err := ioutil.ReadFile(proxyVersionConfig) + if err != nil { + return nil, err + } + proxyVersion := strings.TrimSpace(string(version)) + + proxyDir := filepath.Join(gitRoot, ".proxy") + if err := os.MkdirAll(proxyDir, 0755); err != nil { + return nil, err + } + + proxyPath := filepath.Join(proxyDir, "Azure.Sdk.Tools.TestProxy") + err = ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir) + if err != nil { + return nil, err + } + + proxyLog, err := getProxyLog() + if err != nil { + return nil, err + } + defer proxyLog.Close() + + 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() + }() + + // Give background test proxy instance time to start up + time.Sleep(2 * time.Second) + if cmd.ProcessState != nil && cmd.ProcessState.Exited() { + return nil, fmt.Errorf("test proxy instance failed to start in the allotted time") + } + log.Printf("Started test proxy instance (PID %d) on %s\n", cmd.Process.Pid, options.baseURL()) + + return cmd, nil +} + +func StopTestProxyInstance(proxyCmd *exec.Cmd, options *RecordingOptions) error { + if options == nil { + options = defaultOptions() + } + if proxyCmd == nil { + return nil + } + log.Printf("Stopping test proxy instance (PID %d) on %s\n", proxyCmd.Process.Pid, options.baseURL()) + err := proxyCmd.Process.Kill() + if err != nil { + return err + } + return nil +} \ No newline at end of file diff --git a/sdk/security/keyvault/azadmin/backup/utils_test.go b/sdk/security/keyvault/azadmin/backup/utils_test.go index fe1682255835..e4bbc80601e7 100644 --- a/sdk/security/keyvault/azadmin/backup/utils_test.go +++ b/sdk/security/keyvault/azadmin/backup/utils_test.go @@ -59,7 +59,7 @@ func TestMain(m *testing.M) { token = fakeToken } - err := recording.ResetProxy(nil) + err = recording.ResetProxy(nil) if err != nil { panic(err) } diff --git a/sdk/security/keyvault/azadmin/rbac/utils_test.go b/sdk/security/keyvault/azadmin/rbac/utils_test.go index 82b4a4e8e111..73f2cf8b1be1 100644 --- a/sdk/security/keyvault/azadmin/rbac/utils_test.go +++ b/sdk/security/keyvault/azadmin/rbac/utils_test.go @@ -41,7 +41,7 @@ func TestMain(m *testing.M) { hsmURL = fakeHsmURL } - err := recording.ResetProxy(nil) + err = recording.ResetProxy(nil) if err != nil { panic(err) } diff --git a/sdk/security/keyvault/azadmin/settings/utils_test.go b/sdk/security/keyvault/azadmin/settings/utils_test.go index 55b43241a422..4014f798434a 100644 --- a/sdk/security/keyvault/azadmin/settings/utils_test.go +++ b/sdk/security/keyvault/azadmin/settings/utils_test.go @@ -41,7 +41,7 @@ func TestMain(m *testing.M) { hsmURL = fakeHsmURL } - err := recording.ResetProxy(nil) + err = recording.ResetProxy(nil) if err != nil { panic(err) } From 66b86f10fb1355d3d570f3a018024c58c511bb7f Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Fri, 14 Jul 2023 18:27:18 -0400 Subject: [PATCH 2/8] Update recording tests to use standalone proxy --- sdk/internal/recording/matchers.go | 4 +- sdk/internal/recording/matchers_test.go | 171 +++--- sdk/internal/recording/recording.go | 5 +- sdk/internal/recording/recording_test.go | 371 ++++++------ sdk/internal/recording/sanitizer_test.go | 526 +++++++++--------- sdk/internal/recording/server.go | 5 +- .../TestBackwardSlashPath.json | 0 .../TestGenerateAlphaNumericID.json | 2 +- .../TestModeNotSetStartStop.json | 0 .../TestSetBodilessMatcherNilTest.json | 0 .../{ => TestRecording}/TestStartStop.json | 0 .../TestBodyKeySanitizer.json | 0 .../TestBodyRegexSanitizer.json | 0 .../TestContinuationSanitizer.json | 0 .../TestGeneralRegexSanitizer.json | 0 .../TestHeaderRegexSanitizer.json | 0 .../TestOAuthResponseSanitizer.json | 0 .../TestRemoveHeaderSanitizer.json | 0 .../TestResetSanitizers.json | 0 .../TestSingleTestSanitizer-0.json | 0 .../TestSingleTestSanitizer-1.json | 0 .../TestUriSanitizer.json | 0 .../TestUriSubscriptionIdSanitizer.json | 0 .../recordings/TestSetBodilessMatcher.json | 36 -- .../recordings/TestSetDefaultMatcher.json | 36 -- .../keyvault/azadmin/backup/utils_test.go | 2 +- .../keyvault/azadmin/rbac/utils_test.go | 2 +- .../keyvault/azadmin/settings/utils_test.go | 2 +- 28 files changed, 589 insertions(+), 573 deletions(-) rename sdk/internal/recording/testdata/recordings/{ => TestRecording}/TestBackwardSlashPath.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecording}/TestGenerateAlphaNumericID.json (59%) rename sdk/internal/recording/testdata/recordings/{ => TestRecording}/TestModeNotSetStartStop.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecording}/TestSetBodilessMatcherNilTest.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecording}/TestStartStop.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestBodyKeySanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestBodyRegexSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestContinuationSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestGeneralRegexSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestHeaderRegexSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestOAuthResponseSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestRemoveHeaderSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestResetSanitizers.json (100%) rename sdk/internal/recording/testdata/recordings/{TestSingleTestSanitizer => TestRecordingSanitizer/TestSingleTestSanitizer/TestRecordingSanitizer}/TestSingleTestSanitizer-0.json (100%) rename sdk/internal/recording/testdata/recordings/{TestSingleTestSanitizer => TestRecordingSanitizer/TestSingleTestSanitizer/TestRecordingSanitizer}/TestSingleTestSanitizer-1.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestUriSanitizer.json (100%) rename sdk/internal/recording/testdata/recordings/{ => TestRecordingSanitizer}/TestUriSubscriptionIdSanitizer.json (100%) delete mode 100644 sdk/internal/recording/testdata/recordings/TestSetBodilessMatcher.json delete mode 100644 sdk/internal/recording/testdata/recordings/TestSetDefaultMatcher.json diff --git a/sdk/internal/recording/matchers.go b/sdk/internal/recording/matchers.go index 783524afe720..cc5bb24121ac 100644 --- a/sdk/internal/recording/matchers.go +++ b/sdk/internal/recording/matchers.go @@ -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) } diff --git a/sdk/internal/recording/matchers_test.go b/sdk/internal/recording/matchers_test.go index 015cd6279128..39fc1a5d0dc7 100644 --- a/sdk/internal/recording/matchers_test.go +++ b/sdk/internal/recording/matchers_test.go @@ -9,167 +9,196 @@ package recording import ( "bytes" "net/http" + "os" + "os/exec" "testing" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) -func TestSetBodilessMatcher(t *testing.T) { +type matchersTests struct { + suite.Suite + proxyCmd *exec.Cmd +} + +func TestMatchers(t *testing.T) { + suite.Run(t, new(matchersTests)) +} + +func (s *matchersTests) SetupSuite() { + proxyCmd, err := StartTestProxyInstance(nil) + s.proxyCmd = proxyCmd + require.NoError(s.T(), err) +} + +func (s *matchersTests) TearDownSuite() { + StopTestProxyInstance(s.proxyCmd, nil) + + err := os.RemoveAll("./testdata/recordings/TestMatchers/") + require.NoError(s.T(), err) +} + +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(t, nil) - require.NoError(t, err) + err = SetBodilessMatcher(s.T(), nil) + 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 TestSetBodilessMatcherNilTest(t *testing.T) { +func (s *matchersTests) TestSetBodilessMatcherNilTest() { + 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(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"}))) } diff --git a/sdk/internal/recording/recording.go b/sdk/internal/recording/recording.go index 2d123b9e6dd2..84c3e79d5700 100644 --- a/sdk/internal/recording/recording.go +++ b/sdk/internal/recording/recording.go @@ -673,8 +673,6 @@ func requestStart(url string, testId string, assetConfigLocation string) (*http. return client.Do(req) } -// Start optionally installs and starts a test proxy instance -// and tells the test proxy instance to begin accepting requests for a given test func Start(t *testing.T, pathToRecordings string, options *RecordingOptions) error { if options == nil { options = defaultOptions() @@ -796,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() diff --git a/sdk/internal/recording/recording_test.go b/sdk/internal/recording/recording_test.go index 5d4d6bbc473a..ea7e45f8ee13 100644 --- a/sdk/internal/recording/recording_test.go +++ b/sdk/internal/recording/recording_test.go @@ -13,6 +13,7 @@ import ( "math/rand" "net/http" "os" + "os/exec" "path/filepath" "strings" "testing" @@ -24,14 +25,34 @@ import ( "github.com/stretchr/testify/suite" ) +const packagePath = "sdk/internal/recording/testdata" + type recordingTests struct { suite.Suite + proxyCmd *exec.Cmd } func TestRecording(t *testing.T) { suite.Run(t, new(recordingTests)) } +func (s *recordingTests) SetupSuite() { + proxyCmd, err := StartTestProxyInstance(nil) + s.proxyCmd = proxyCmd + require.NoError(s.T(), err) +} + +func (s *recordingTests) TearDownSuite() { + StopTestProxyInstance(s.proxyCmd, nil) + + 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 +260,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 +391,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") -var packagePath = "sdk/internal/recording/testdata" + r = *defaultOptions() + require.Equal(r.baseURL(), fmt.Sprintf("https://localhost:%d", r.ProxyPort)) + // ProxyPort should be generated deterministically + require.Equal(r.ProxyPort, defaultOptions().ProxyPort) +} -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 = Stop(t, nil) - require.NoError(t, err) -} + err := Start(s.T(), packagePathBackslash, nil) + require.NoError(err) -func TestLiveOnly(t *testing.T) { - require.Equal(t, IsLiveOnly(t), false) - LiveOnly(t) - require.Equal(t, IsLiveOnly(t), true) + err = Stop(s.T(), nil) + require.NoError(err) } -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 (s *recordingTests) TestLiveOnly() { + require := require.New(s.T()) + require.Equal(IsLiveOnly(s.T()), false) + LiveOnly(s.T()) + require.Equal(IsLiveOnly(s.T()), true) } -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 +607,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 +640,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) } diff --git a/sdk/internal/recording/sanitizer_test.go b/sdk/internal/recording/sanitizer_test.go index 4313a4c0b5a3..788379a884ae 100644 --- a/sdk/internal/recording/sanitizer_test.go +++ b/sdk/internal/recording/sanitizer_test.go @@ -13,6 +13,7 @@ import ( "io" "net/http" "os" + "os/exec" "strings" "testing" @@ -25,6 +26,7 @@ import ( type sanitizerTests struct { suite.Suite + proxyCmd *exec.Cmd } const authHeader string = "Authorization" @@ -36,6 +38,19 @@ func TestRecordingSanitizer(t *testing.T) { suite.Run(t, new(sanitizerTests)) } +func (s *sanitizerTests) SetupSuite() { + proxyCmd, err := StartTestProxyInstance(nil) + s.proxyCmd = proxyCmd + require.NoError(s.T(), err) +} + +func (s *sanitizerTests) TearDownSuite() { + StopTestProxyInstance(s.proxyCmd, nil) + // cleanup test files + err := os.RemoveAll("testfiles") + require.NoError(s.T(), err) +} + func (s *sanitizerTests) TestDefaultSanitizerSanitizesAuthHeader() { require := require.New(s.T()) server, cleanup := mock.NewServer() @@ -140,13 +155,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 +199,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\\:\\/\\/(?[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\\:\\/\\/(?[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\\:\\/\\/(?[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\\:\\/\\/(?[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\\:\\/\\/(?[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 +772,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") } }) } diff --git a/sdk/internal/recording/server.go b/sdk/internal/recording/server.go index 154768ab4661..dea114734c0e 100644 --- a/sdk/internal/recording/server.go +++ b/sdk/internal/recording/server.go @@ -25,6 +25,7 @@ import ( ) func getTestProxyDownloadFile() (string, error) { + // No ARM binaries for Windows, so return x64 if runtime.GOOS == "windows" { return "test-proxy-standalone-win-x64.zip", nil } @@ -47,7 +48,7 @@ func extractTestProxyZip(archivePath string, outputDir string) error { // Open the zip file r, err := zip.OpenReader(archivePath) if err != nil { - panic(err) + return err } defer r.Close() @@ -158,8 +159,8 @@ func ensureTestProxyInstalled(proxyVersion string, proxyPath string, proxyDir st // 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() { - os.Remove(lockFile) lock.Close() + os.Remove(lockFile) }() break diff --git a/sdk/internal/recording/testdata/recordings/TestBackwardSlashPath.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestBackwardSlashPath.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestBackwardSlashPath.json rename to sdk/internal/recording/testdata/recordings/TestRecording/TestBackwardSlashPath.json diff --git a/sdk/internal/recording/testdata/recordings/TestGenerateAlphaNumericID.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json similarity index 59% rename from sdk/internal/recording/testdata/recordings/TestGenerateAlphaNumericID.json rename to sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json index 412797423e84..90a19356146b 100644 --- a/sdk/internal/recording/testdata/recordings/TestGenerateAlphaNumericID.json +++ b/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json @@ -1,6 +1,6 @@ { "Entries": [], "Variables": { - "randSeed": "1676365801" + "randSeed": "1689377888" } } diff --git a/sdk/internal/recording/testdata/recordings/TestModeNotSetStartStop.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestModeNotSetStartStop.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestModeNotSetStartStop.json rename to sdk/internal/recording/testdata/recordings/TestRecording/TestModeNotSetStartStop.json diff --git a/sdk/internal/recording/testdata/recordings/TestSetBodilessMatcherNilTest.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestSetBodilessMatcherNilTest.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestSetBodilessMatcherNilTest.json rename to sdk/internal/recording/testdata/recordings/TestRecording/TestSetBodilessMatcherNilTest.json diff --git a/sdk/internal/recording/testdata/recordings/TestStartStop.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestStartStop.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestStartStop.json rename to sdk/internal/recording/testdata/recordings/TestRecording/TestStartStop.json diff --git a/sdk/internal/recording/testdata/recordings/TestBodyKeySanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestBodyKeySanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestBodyKeySanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestBodyKeySanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestBodyRegexSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestBodyRegexSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestBodyRegexSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestBodyRegexSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestContinuationSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestContinuationSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestContinuationSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestContinuationSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestGeneralRegexSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestGeneralRegexSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestGeneralRegexSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestGeneralRegexSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestHeaderRegexSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestHeaderRegexSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestHeaderRegexSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestHeaderRegexSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestOAuthResponseSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestOAuthResponseSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestOAuthResponseSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestOAuthResponseSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestRemoveHeaderSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestRemoveHeaderSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestRemoveHeaderSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestRemoveHeaderSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestResetSanitizers.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestResetSanitizers.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestResetSanitizers.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestResetSanitizers.json diff --git a/sdk/internal/recording/testdata/recordings/TestSingleTestSanitizer/TestSingleTestSanitizer-0.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestSingleTestSanitizer/TestRecordingSanitizer/TestSingleTestSanitizer-0.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestSingleTestSanitizer/TestSingleTestSanitizer-0.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestSingleTestSanitizer/TestRecordingSanitizer/TestSingleTestSanitizer-0.json diff --git a/sdk/internal/recording/testdata/recordings/TestSingleTestSanitizer/TestSingleTestSanitizer-1.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestSingleTestSanitizer/TestRecordingSanitizer/TestSingleTestSanitizer-1.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestSingleTestSanitizer/TestSingleTestSanitizer-1.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestSingleTestSanitizer/TestRecordingSanitizer/TestSingleTestSanitizer-1.json diff --git a/sdk/internal/recording/testdata/recordings/TestUriSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestUriSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestUriSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestUriSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestUriSubscriptionIdSanitizer.json b/sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestUriSubscriptionIdSanitizer.json similarity index 100% rename from sdk/internal/recording/testdata/recordings/TestUriSubscriptionIdSanitizer.json rename to sdk/internal/recording/testdata/recordings/TestRecordingSanitizer/TestUriSubscriptionIdSanitizer.json diff --git a/sdk/internal/recording/testdata/recordings/TestSetBodilessMatcher.json b/sdk/internal/recording/testdata/recordings/TestSetBodilessMatcher.json deleted file mode 100644 index 75314a29eabb..000000000000 --- a/sdk/internal/recording/testdata/recordings/TestSetBodilessMatcher.json +++ /dev/null @@ -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": {} -} diff --git a/sdk/internal/recording/testdata/recordings/TestSetDefaultMatcher.json b/sdk/internal/recording/testdata/recordings/TestSetDefaultMatcher.json deleted file mode 100644 index cc800e3e0c7e..000000000000 --- a/sdk/internal/recording/testdata/recordings/TestSetDefaultMatcher.json +++ /dev/null @@ -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": {} -} diff --git a/sdk/security/keyvault/azadmin/backup/utils_test.go b/sdk/security/keyvault/azadmin/backup/utils_test.go index e4bbc80601e7..1ef99bedfbd1 100644 --- a/sdk/security/keyvault/azadmin/backup/utils_test.go +++ b/sdk/security/keyvault/azadmin/backup/utils_test.go @@ -59,7 +59,7 @@ func TestMain(m *testing.M) { token = fakeToken } - err = recording.ResetProxy(nil) + err := recording.ResetProxy(nil) if err != nil { panic(err) } diff --git a/sdk/security/keyvault/azadmin/rbac/utils_test.go b/sdk/security/keyvault/azadmin/rbac/utils_test.go index 73f2cf8b1be1..7af2ceeb04c8 100644 --- a/sdk/security/keyvault/azadmin/rbac/utils_test.go +++ b/sdk/security/keyvault/azadmin/rbac/utils_test.go @@ -41,7 +41,7 @@ func TestMain(m *testing.M) { hsmURL = fakeHsmURL } - err = recording.ResetProxy(nil) + err := recording.ResetProxy(nil) if err != nil { panic(err) } diff --git a/sdk/security/keyvault/azadmin/settings/utils_test.go b/sdk/security/keyvault/azadmin/settings/utils_test.go index 4014f798434a..999786f7d2bd 100644 --- a/sdk/security/keyvault/azadmin/settings/utils_test.go +++ b/sdk/security/keyvault/azadmin/settings/utils_test.go @@ -41,7 +41,7 @@ func TestMain(m *testing.M) { hsmURL = fakeHsmURL } - err = recording.ResetProxy(nil) + err := recording.ResetProxy(nil) if err != nil { panic(err) } From e4f8ad1323fc46d5e25600877a242fa052a40106 Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Fri, 14 Jul 2023 20:39:17 -0400 Subject: [PATCH 3/8] Simplify proxy binary switch statement --- sdk/internal/recording/server.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sdk/internal/recording/server.go b/sdk/internal/recording/server.go index dea114734c0e..c214d42522f3 100644 --- a/sdk/internal/recording/server.go +++ b/sdk/internal/recording/server.go @@ -25,12 +25,10 @@ import ( ) func getTestProxyDownloadFile() (string, error) { - // No ARM binaries for Windows, so return x64 - if runtime.GOOS == "windows" { - return "test-proxy-standalone-win-x64.zip", nil - } - 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": From e728994caa020be735d290005a56e41f15850f31 Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Mon, 17 Jul 2023 15:51:35 -0400 Subject: [PATCH 4/8] Add test proxy auto-install docs --- documentation/developer_setup.md | 3 ++- sdk/internal/recording/README.md | 26 ++++++++++++++++++- .../keyvault/azadmin/backup/utils_test.go | 2 +- .../keyvault/azadmin/rbac/utils_test.go | 2 +- .../keyvault/azadmin/settings/utils_test.go | 2 +- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/documentation/developer_setup.md b/documentation/developer_setup.md index 12e5e93bea5d..2cf8f1fdcc20 100644 --- a/documentation/developer_setup.md +++ b/documentation/developer_setup.md @@ -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 diff --git a/sdk/internal/recording/README.md b/sdk/internal/recording/README.md index 3b7b5ed309c0..25fab7efb37c 100644 --- a/sdk/internal/recording/README.md +++ b/sdk/internal/recording/README.md @@ -16,9 +16,33 @@ After you've set the `AZURE_RECORD_MODE`, set the `PROXY_CERT` environment varia $ENV:PROXY_CERT="C:/ /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) { + proxyCmd, err := StartTestProxyInstance(nil) + if err != nil { + panic(err) + } + + ... all other test code, including proxy recording setup ... + + code := m.Run() + + StopTestProxyInstance(proxyCmd, nil) + 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: diff --git a/sdk/security/keyvault/azadmin/backup/utils_test.go b/sdk/security/keyvault/azadmin/backup/utils_test.go index 1ef99bedfbd1..fe1682255835 100644 --- a/sdk/security/keyvault/azadmin/backup/utils_test.go +++ b/sdk/security/keyvault/azadmin/backup/utils_test.go @@ -59,7 +59,7 @@ func TestMain(m *testing.M) { token = fakeToken } - err := recording.ResetProxy(nil) + err := recording.ResetProxy(nil) if err != nil { panic(err) } diff --git a/sdk/security/keyvault/azadmin/rbac/utils_test.go b/sdk/security/keyvault/azadmin/rbac/utils_test.go index 7af2ceeb04c8..82b4a4e8e111 100644 --- a/sdk/security/keyvault/azadmin/rbac/utils_test.go +++ b/sdk/security/keyvault/azadmin/rbac/utils_test.go @@ -41,7 +41,7 @@ func TestMain(m *testing.M) { hsmURL = fakeHsmURL } - err := recording.ResetProxy(nil) + err := recording.ResetProxy(nil) if err != nil { panic(err) } diff --git a/sdk/security/keyvault/azadmin/settings/utils_test.go b/sdk/security/keyvault/azadmin/settings/utils_test.go index 999786f7d2bd..55b43241a422 100644 --- a/sdk/security/keyvault/azadmin/settings/utils_test.go +++ b/sdk/security/keyvault/azadmin/settings/utils_test.go @@ -41,7 +41,7 @@ func TestMain(m *testing.M) { hsmURL = fakeHsmURL } - err := recording.ResetProxy(nil) + err := recording.ResetProxy(nil) if err != nil { panic(err) } From ff787f9f061e731ff9c0b95f320d6541c20bf3f3 Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Mon, 17 Jul 2023 19:25:07 -0400 Subject: [PATCH 5/8] Fix up recording test coverage --- sdk/internal/recording/README.md | 4 +- sdk/internal/recording/matchers_test.go | 15 ++-- sdk/internal/recording/recording.go | 2 +- sdk/internal/recording/recording_test.go | 10 +-- sdk/internal/recording/sanitizer_test.go | 15 ++-- sdk/internal/recording/server.go | 36 ++++++--- sdk/internal/recording/server_test.go | 75 +++++++++++++++++++ .../TestGenerateAlphaNumericID.json | 2 +- 8 files changed, 122 insertions(+), 37 deletions(-) create mode 100644 sdk/internal/recording/server_test.go diff --git a/sdk/internal/recording/README.md b/sdk/internal/recording/README.md index 25fab7efb37c..aa8e31390a02 100644 --- a/sdk/internal/recording/README.md +++ b/sdk/internal/recording/README.md @@ -22,7 +22,7 @@ Recording and playing back tests relies on the [Test Proxy](https://github.com/A ```golang func TestMain(m *testing.M) { - proxyCmd, err := StartTestProxyInstance(nil) + proxy, err := recording.StartTestProxy(nil) if err != nil { panic(err) } @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { code := m.Run() - StopTestProxyInstance(proxyCmd, nil) + recording.StopTestProxy(proxy) if err != nil { panic(err) } diff --git a/sdk/internal/recording/matchers_test.go b/sdk/internal/recording/matchers_test.go index 39fc1a5d0dc7..d46fab230e76 100644 --- a/sdk/internal/recording/matchers_test.go +++ b/sdk/internal/recording/matchers_test.go @@ -10,7 +10,6 @@ import ( "bytes" "net/http" "os" - "os/exec" "testing" "github.com/stretchr/testify/require" @@ -19,7 +18,7 @@ import ( type matchersTests struct { suite.Suite - proxyCmd *exec.Cmd + proxy *TestProxyInstance } func TestMatchers(t *testing.T) { @@ -27,16 +26,16 @@ func TestMatchers(t *testing.T) { } func (s *matchersTests) SetupSuite() { - proxyCmd, err := StartTestProxyInstance(nil) - s.proxyCmd = proxyCmd + proxy, err := StartTestProxy(nil) + s.proxy = proxy require.NoError(s.T(), err) } func (s *matchersTests) TearDownSuite() { - StopTestProxyInstance(s.proxyCmd, nil) - - err := os.RemoveAll("./testdata/recordings/TestMatchers/") - require.NoError(s.T(), err) + err1 := StopTestProxy(s.proxy) + err2 := os.RemoveAll("./testdata/recordings/TestMatchers/") + require.NoError(s.T(), err1) + require.NoError(s.T(), err2) } func (s *matchersTests) TestSetBodilessMatcher() { diff --git a/sdk/internal/recording/recording.go b/sdk/internal/recording/recording.go index 84c3e79d5700..fd15bfe758f5 100644 --- a/sdk/internal/recording/recording.go +++ b/sdk/internal/recording/recording.go @@ -595,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 diff --git a/sdk/internal/recording/recording_test.go b/sdk/internal/recording/recording_test.go index ea7e45f8ee13..267cbfdc82a0 100644 --- a/sdk/internal/recording/recording_test.go +++ b/sdk/internal/recording/recording_test.go @@ -13,7 +13,6 @@ import ( "math/rand" "net/http" "os" - "os/exec" "path/filepath" "strings" "testing" @@ -29,7 +28,7 @@ const packagePath = "sdk/internal/recording/testdata" type recordingTests struct { suite.Suite - proxyCmd *exec.Cmd + proxy *TestProxyInstance } func TestRecording(t *testing.T) { @@ -37,13 +36,14 @@ func TestRecording(t *testing.T) { } func (s *recordingTests) SetupSuite() { - proxyCmd, err := StartTestProxyInstance(nil) - s.proxyCmd = proxyCmd + proxy, err := StartTestProxy(nil) + s.proxy = proxy require.NoError(s.T(), err) } func (s *recordingTests) TearDownSuite() { - StopTestProxyInstance(s.proxyCmd, nil) + stopErr := StopTestProxy(s.proxy) + require.NoError(s.T(), stopErr) files, err := filepath.Glob("recordings/**/*.yaml") require.NoError(s.T(), err) diff --git a/sdk/internal/recording/sanitizer_test.go b/sdk/internal/recording/sanitizer_test.go index 788379a884ae..b92dc6954fdd 100644 --- a/sdk/internal/recording/sanitizer_test.go +++ b/sdk/internal/recording/sanitizer_test.go @@ -13,7 +13,6 @@ import ( "io" "net/http" "os" - "os/exec" "strings" "testing" @@ -26,7 +25,7 @@ import ( type sanitizerTests struct { suite.Suite - proxyCmd *exec.Cmd + proxy *TestProxyInstance } const authHeader string = "Authorization" @@ -39,16 +38,16 @@ func TestRecordingSanitizer(t *testing.T) { } func (s *sanitizerTests) SetupSuite() { - proxyCmd, err := StartTestProxyInstance(nil) - s.proxyCmd = proxyCmd + proxy, err := StartTestProxy(nil) + s.proxy = proxy require.NoError(s.T(), err) } func (s *sanitizerTests) TearDownSuite() { - StopTestProxyInstance(s.proxyCmd, nil) - // cleanup test files - err := os.RemoveAll("testfiles") - require.NoError(s.T(), err) + err1 := StopTestProxy(s.proxy) + err2 := os.RemoveAll("testfiles") + require.NoError(s.T(), err1) + require.NoError(s.T(), err2) } func (s *sanitizerTests) TestDefaultSanitizerSanitizesAuthHeader() { diff --git a/sdk/internal/recording/server.go b/sdk/internal/recording/server.go index c214d42522f3..efce0bf657b2 100644 --- a/sdk/internal/recording/server.go +++ b/sdk/internal/recording/server.go @@ -24,6 +24,11 @@ import ( "time" ) +type TestProxyInstance struct { + Cmd *exec.Cmd + Options *RecordingOptions +} + func getTestProxyDownloadFile() (string, error) { switch { case runtime.GOOS == "windows": @@ -242,7 +247,18 @@ func getProxyLog() (*os.File, error) { return proxyLog, nil } -func StartTestProxyInstance(options *RecordingOptions) (*exec.Cmd, error) { +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 StartTestProxy(options *RecordingOptions) (*TestProxyInstance, error) { manualStart := strings.ToLower(os.Getenv("PROXY_MANUAL_START")) if manualStart == "true" { log.Println("PROXY_MANUAL_START env variable is set to true, not starting test proxy...") @@ -257,12 +273,11 @@ func StartTestProxyInstance(options *RecordingOptions) (*exec.Cmd, error) { if err != nil { return nil, err } - proxyVersionConfig := filepath.Join(gitRoot, "eng/common/testproxy/target_version.txt") - version, err := ioutil.ReadFile(proxyVersionConfig) + + proxyVersion, err := getProxyVersion(gitRoot) if err != nil { return nil, err } - proxyVersion := strings.TrimSpace(string(version)) proxyDir := filepath.Join(gitRoot, ".proxy") if err := os.MkdirAll(proxyDir, 0755); err != nil { @@ -309,18 +324,15 @@ func StartTestProxyInstance(options *RecordingOptions) (*exec.Cmd, error) { } log.Printf("Started test proxy instance (PID %d) on %s\n", cmd.Process.Pid, options.baseURL()) - return cmd, nil + return &TestProxyInstance{Cmd: cmd, Options: options}, nil } -func StopTestProxyInstance(proxyCmd *exec.Cmd, options *RecordingOptions) error { - if options == nil { - options = defaultOptions() - } - if proxyCmd == nil { +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", proxyCmd.Process.Pid, options.baseURL()) - err := proxyCmd.Process.Kill() + 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 } diff --git a/sdk/internal/recording/server_test.go b/sdk/internal/recording/server_test.go new file mode 100644 index 000000000000..56b934abf4ee --- /dev/null +++ b/sdk/internal/recording/server_test.go @@ -0,0 +1,75 @@ +//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" + "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) 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") + + // Test download proxy + ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir) + require.NoError(s.T(), err) + + stat1, err := os.Stat(proxyPath) + require.NoError(s.T(), err) + + // Test cached proxy + 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") +} \ No newline at end of file diff --git a/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json index 90a19356146b..b866ecb7863f 100644 --- a/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json +++ b/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json @@ -1,6 +1,6 @@ { "Entries": [], "Variables": { - "randSeed": "1689377888" + "randSeed": "1689636092" } } From 7a597895d6469113dafd4f046d94c82bb928b6c3 Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Tue, 18 Jul 2023 15:26:18 -0400 Subject: [PATCH 6/8] Add StopTestProxy note about go process handling --- sdk/internal/recording/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/internal/recording/server.go b/sdk/internal/recording/server.go index efce0bf657b2..f3d8ed3c8c98 100644 --- a/sdk/internal/recording/server.go +++ b/sdk/internal/recording/server.go @@ -327,6 +327,9 @@ func StartTestProxy(options *RecordingOptions) (*TestProxyInstance, error) { return &TestProxyInstance{Cmd: cmd, Options: options}, nil } +// 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 From ee6260d200634aac1e602bf255a8d5a09ffbf36e Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Wed, 19 Jul 2023 21:03:18 -0400 Subject: [PATCH 7/8] Proxy restore/race condition handling. Force ignore PROXY_MANUAL_START in internal tests --- sdk/internal/perf/recording_test.go | 23 +- sdk/internal/recording/matchers_test.go | 4 +- sdk/internal/recording/recording_test.go | 4 +- sdk/internal/recording/sanitizer_test.go | 2 +- sdk/internal/recording/server.go | 349 ++++++++++++------ sdk/internal/recording/server_test.go | 25 +- .../TestGenerateAlphaNumericID.json | 2 +- 7 files changed, 271 insertions(+), 138 deletions(-) diff --git a/sdk/internal/perf/recording_test.go b/sdk/internal/perf/recording_test.go index 650cbb54cf29..a12a05c9df56 100644 --- a/sdk/internal/perf/recording_test.go +++ b/sdk/internal/perf/recording_test.go @@ -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) diff --git a/sdk/internal/recording/matchers_test.go b/sdk/internal/recording/matchers_test.go index d46fab230e76..be15e56999cf 100644 --- a/sdk/internal/recording/matchers_test.go +++ b/sdk/internal/recording/matchers_test.go @@ -26,7 +26,9 @@ func TestMatchers(t *testing.T) { } func (s *matchersTests) SetupSuite() { - proxy, err := StartTestProxy(nil) + // 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) } diff --git a/sdk/internal/recording/recording_test.go b/sdk/internal/recording/recording_test.go index 267cbfdc82a0..4c46620d5491 100644 --- a/sdk/internal/recording/recording_test.go +++ b/sdk/internal/recording/recording_test.go @@ -36,7 +36,9 @@ func TestRecording(t *testing.T) { } func (s *recordingTests) SetupSuite() { - proxy, err := StartTestProxy(nil) + // 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) } diff --git a/sdk/internal/recording/sanitizer_test.go b/sdk/internal/recording/sanitizer_test.go index b92dc6954fdd..dd9584407efa 100644 --- a/sdk/internal/recording/sanitizer_test.go +++ b/sdk/internal/recording/sanitizer_test.go @@ -38,7 +38,7 @@ func TestRecordingSanitizer(t *testing.T) { } func (s *sanitizerTests) SetupSuite() { - proxy, err := StartTestProxy(nil) + proxy, err := StartTestProxy("", nil) s.proxy = proxy require.NoError(s.T(), err) } diff --git a/sdk/internal/recording/server.go b/sdk/internal/recording/server.go index f3d8ed3c8c98..b162b6650c04 100644 --- a/sdk/internal/recording/server.go +++ b/sdk/internal/recording/server.go @@ -19,13 +19,15 @@ import ( "os" "os/exec" "path/filepath" - "strings" "runtime" + "strings" "time" ) +const ProxyManualStartEnv = "PROXY_MANUAL_START" + type TestProxyInstance struct { - Cmd *exec.Cmd + Cmd *exec.Cmd Options *RecordingOptions } @@ -47,40 +49,75 @@ func getTestProxyDownloadFile() (string, error) { } } -func extractTestProxyZip(archivePath string, outputDir string) error { - // Open the zip file - r, err := zip.OpenReader(archivePath) - if err != nil { - return err - } - defer r.Close() - - for _, f := range r.File { - targetPath := filepath.Join(outputDir, f.Name) - - log.Println("Extracting", targetPath) - - if f.FileInfo().IsDir() { - os.MkdirAll(targetPath, f.Mode()) - continue - } - - file, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - defer file.Close() - - rc, err := f.Open() - if err != nil { - return err - } - defer rc.Close() - - if _, err = io.Copy(file, rc); err != nil { - return err - } - } +// 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 } @@ -89,64 +126,99 @@ 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() + return err + } + defer file.Close() gzipReader, err := gzip.NewReader(file) - if err != nil { - return err - } - defer gzipReader.Close() + 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) - } + 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 extractTestProxy(archivePath string, outputDir string) error { +func installTestProxy(archivePath string, outputDir string, proxyPath string) error { + var err error if strings.HasSuffix(archivePath, ".zip") { - return extractTestProxyZip(archivePath, outputDir) + err = extractTestProxyZip(archivePath, outputDir) } else { - return extractTestProxyArchive(archivePath, outputDir) + 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) error { +func ensureTestProxyInstalled(proxyVersion string, proxyPath string, proxyDir string, pathToRecordings string) error { lockFile := filepath.Join(os.TempDir(), "test-proxy-install.lock") - maxTries := 600 // Wait 1 minute + 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) @@ -174,18 +246,18 @@ func ensureTestProxyInstalled(proxyVersion string, proxyPath string, proxyDir st } cmd := exec.Command(proxyPath, "--version") - out, err := cmd.Output() - if err != nil { + 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 nil + return restoreRecordings(proxyPath, pathToRecordings) } else { log.Printf("Test proxy version %s does not match required version %s\n", - installedVersion, proxyVersion) + installedVersion, proxyVersion) } } @@ -195,41 +267,39 @@ func ensureTestProxyInstalled(proxyVersion string, proxyPath string, proxyDir st } proxyDownloadPath := filepath.Join(proxyDir, proxyFile) - archive, err := os.Create(proxyDownloadPath) - if err != nil { - return err - } - defer archive.Close() + 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) + 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 - } - defer resp.Body.Close() - - _, err = io.Copy(archive, resp.Body) - if err != nil { - return err - } - - err = extractTestProxy(proxyDownloadPath, proxyDir) + proxyVersion, proxyFile) + resp, err := http.Get(proxyUrl) if err != nil { return err } - err = os.Chmod(proxyPath, 0755) + + _, err = io.Copy(archive, resp.Body) if err != nil { return err } - err = os.Remove(proxyDownloadPath) + err = resp.Body.Close() + if err != nil { + return err + } + err = archive.Close() if err != nil { return err } - return nil + err = installTestProxy(proxyDownloadPath, proxyDir, proxyPath) + if err != nil { + return err + } + + return restoreRecordings(proxyPath, pathToRecordings) } func getProxyLog() (*os.File, error) { @@ -239,7 +309,7 @@ func getProxyLog() (*os.File, error) { for i := range suffix { suffix[i] = letters[rand.Intn(len(letters))] } - proxyLogName := fmt.Sprintf("testproxy.log.%s", suffix) + proxyLogName := fmt.Sprintf("test-proxy.log.%s", suffix) proxyLog, err := os.Create(filepath.Join(os.TempDir(), proxyLogName)) if err != nil { return nil, err @@ -258,10 +328,47 @@ func getProxyVersion(gitRoot string) (string, error) { return proxyVersion, nil } -func StartTestProxy(options *RecordingOptions) (*TestProxyInstance, error) { - manualStart := strings.ToLower(os.Getenv("PROXY_MANUAL_START")) +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.Println("PROXY_MANUAL_START env variable is set to true, not starting test proxy...") + log.Printf("%s env variable is set to true, not starting test proxy...\n", ProxyManualStartEnv) return nil, nil } @@ -278,14 +385,15 @@ func StartTestProxy(options *RecordingOptions) (*TestProxyInstance, error) { 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") - err = ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir) + 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 } @@ -296,14 +404,16 @@ func StartTestProxy(options *RecordingOptions) (*TestProxyInstance, error) { } 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()) + 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()) + proxyPath, "start", "--storage-location", gitRoot, "--", "--urls="+options.baseURL()) cmd.Stdout = proxyLog cmd.Stderr = proxyLog @@ -317,14 +427,7 @@ func StartTestProxy(options *RecordingOptions) (*TestProxyInstance, error) { done <- cmd.Wait() }() - // Give background test proxy instance time to start up - time.Sleep(2 * time.Second) - if cmd.ProcessState != nil && cmd.ProcessState.Exited() { - return nil, fmt.Errorf("test proxy instance failed to start in the allotted time") - } - log.Printf("Started test proxy instance (PID %d) on %s\n", cmd.Process.Pid, options.baseURL()) - - return &TestProxyInstance{Cmd: cmd, Options: options}, nil + return waitForProxyStart(cmd, options) } // NOTE: The process will be killed if the user hits ctrl-c mid-way through tests, as go will @@ -340,4 +443,4 @@ func StopTestProxy(proxyInstance *TestProxyInstance) error { return err } return nil -} \ No newline at end of file +} diff --git a/sdk/internal/recording/server_test.go b/sdk/internal/recording/server_test.go index 56b934abf4ee..0cd77d61d2bd 100644 --- a/sdk/internal/recording/server_test.go +++ b/sdk/internal/recording/server_test.go @@ -10,6 +10,7 @@ import ( "archive/zip" "os" "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/require" @@ -24,6 +25,11 @@ 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) @@ -33,11 +39,11 @@ func (s *serverTests) TestProxyDownloadFile() { func (s *serverTests) TestExtractTestProxyZip() { zipFile, err := os.CreateTemp("", "test-extract-*.zip") require.NoError(s.T(), err) - defer zipFile.Close() + defer zipFile.Close() - // Create a new zip archive - zipWriter := zip.NewWriter(zipFile) - defer zipWriter.Close() + // Create a new zip archive + zipWriter := zip.NewWriter(zipFile) + defer zipWriter.Close() } func (s *serverTests) TestEnsureTestProxyInstalled() { @@ -55,21 +61,24 @@ func (s *serverTests) TestEnsureTestProxyInstalled() { err = os.MkdirAll(proxyDir, 0755) require.NoError(s.T(), err) - proxyPath := filepath.Join(proxyDir, "Azure.Sdk.Tools.TestProxy") + proxyPath := filepath.Join(proxyDir, "Azure.Sdk.Tools.TestProxy") + if runtime.GOOS == "windows" { + proxyPath += ".exe" + } // Test download proxy - ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir) + err = ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir, "") require.NoError(s.T(), err) stat1, err := os.Stat(proxyPath) require.NoError(s.T(), err) // Test cached proxy - ensureTestProxyInstalled(proxyVersion, proxyPath, proxyDir) + 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") -} \ No newline at end of file +} diff --git a/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json b/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json index b866ecb7863f..0253f69888b3 100644 --- a/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json +++ b/sdk/internal/recording/testdata/recordings/TestRecording/TestGenerateAlphaNumericID.json @@ -1,6 +1,6 @@ { "Entries": [], "Variables": { - "randSeed": "1689636092" + "randSeed": "1689722394" } } From 3ff75587991236833077ebd191b3f265d0ccb572 Mon Sep 17 00:00:00 2001 From: Ben Broderick Phillips Date: Thu, 20 Jul 2023 17:13:05 -0400 Subject: [PATCH 8/8] Fix recording readme error handling --- sdk/internal/recording/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/internal/recording/README.md b/sdk/internal/recording/README.md index aa8e31390a02..2c65de5e3903 100644 --- a/sdk/internal/recording/README.md +++ b/sdk/internal/recording/README.md @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { code := m.Run() - recording.StopTestProxy(proxy) + err = recording.StopTestProxy(proxy) if err != nil { panic(err) }