From fb33bb88e42ed3a7c723c06976970f0aeff10052 Mon Sep 17 00:00:00 2001 From: Joel Hendrix Date: Tue, 18 Jul 2023 15:15:14 -0700 Subject: [PATCH] Formalize contract for fakes sanitizing request URLs The current fakes codegen makes assumptions about the faked URL format. Make the faked next page URL a bit more unique/obvious it's a fake. Propagate any query parameters from the original request's URL to the faked URL. --- sdk/azcore/CHANGELOG.md | 3 +++ sdk/azcore/fake/internal/exported/fake.go | 19 ++++++++++++++- .../fake/internal/exported/fake_test.go | 4 ++++ sdk/azcore/fake/server/server.go | 9 ++++++++ sdk/azcore/fake/server/server_test.go | 5 ++++ sdk/azcore/internal/pollers/fake/fake.go | 23 +++++++++++++++---- sdk/azcore/internal/pollers/fake/fake_test.go | 4 ++++ 7 files changed, 62 insertions(+), 5 deletions(-) diff --git a/sdk/azcore/CHANGELOG.md b/sdk/azcore/CHANGELOG.md index bbe999c43136..ec1be4cd20f8 100644 --- a/sdk/azcore/CHANGELOG.md +++ b/sdk/azcore/CHANGELOG.md @@ -4,11 +4,14 @@ ### Features Added +* Added function `SanitizePagerPollerPath` to the `server` package to centralize sanitization and formalize the contract. + ### Breaking Changes ### Bugs Fixed * Enable TLS renegotiation in the default transport policy. +* Propagate any query parameters when constructing a fake poller and/or injecting next links. ### Other Changes diff --git a/sdk/azcore/fake/internal/exported/fake.go b/sdk/azcore/fake/internal/exported/fake.go index 11bb20290016..7aabebc120a2 100644 --- a/sdk/azcore/fake/internal/exported/fake.go +++ b/sdk/azcore/fake/internal/exported/fake.go @@ -13,6 +13,7 @@ import ( "fmt" "io" "net/http" + "regexp" "strconv" "github.com/Azure/azure-sdk-for-go/sdk/azcore" @@ -179,6 +180,9 @@ type pageindex[T any] struct { page pageResp[T] } +// nextLinkURLSuffix is the URL path suffix for a faked next page followed by one or more digits. +const nextLinkURLSuffix = "/fake_page_" + // InjectNextLinks is used to populate the nextLink field. // The inject callback is executed for every T in the sequence except for the last one. // This function is called by the fake server internals. @@ -201,8 +205,14 @@ func (p *PagerResponder[T]) InjectNextLinks(req *http.Request, inject func(page break } + qp := "" + if req.URL.RawQuery != "" { + qp = "?" + req.URL.RawQuery + } + inject(&pages[i].page.entry, func() string { - return fmt.Sprintf("%s://%s%s/page_%d", req.URL.Scheme, req.URL.Host, req.URL.Path, i+1) + // NOTE: any changes to this path format MUST be reflected in SanitizePagerPath() + return fmt.Sprintf("%s://%s%s%s%d%s", req.URL.Scheme, req.URL.Host, req.URL.Path, nextLinkURLSuffix, i+1, qp) }) // update the original slice with the modified page @@ -384,6 +394,13 @@ func NewResponse(content ResponseContent, req *http.Request) (*http.Response, er }, nil } +var pageSuffixRegex = regexp.MustCompile(nextLinkURLSuffix + `\d+$`) + +// SanitizePagerPath removes any fake-appended suffix from a URL's path. +func SanitizePagerPath(path string) string { + return pageSuffixRegex.ReplaceAllLiteralString(path, "") +} + func newErrorResponse(statusCode int, errorCode string, req *http.Request) (*http.Response, error) { content := ResponseContent{ HTTPStatus: statusCode, diff --git a/sdk/azcore/fake/internal/exported/fake_test.go b/sdk/azcore/fake/internal/exported/fake_test.go index 5bcd9ad50c11..48dc8b08b8fd 100644 --- a/sdk/azcore/fake/internal/exported/fake_test.go +++ b/sdk/azcore/fake/internal/exported/fake_test.go @@ -12,6 +12,7 @@ import ( "io" "net/http" "net/url" + "strings" "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" @@ -120,6 +121,9 @@ func TestPagerResponder(t *testing.T) { page, err := unmarshal[widgets](resp) require.NoError(t, err) require.NotNil(t, page.NextPage) + sanitizedNextPage := SanitizePagerPath(*page.NextPage) + require.NotEqualValues(t, sanitizedNextPage, *page.NextPage) + require.True(t, strings.HasPrefix(*page.NextPage, sanitizedNextPage)) require.Equal(t, []widget{{Name: "foo"}, {Name: "bar"}}, page.Widgets) case 2: require.Error(t, err) diff --git a/sdk/azcore/fake/server/server.go b/sdk/azcore/fake/server/server.go index a43d88b9dc3e..cdf66941b5f8 100644 --- a/sdk/azcore/fake/server/server.go +++ b/sdk/azcore/fake/server/server.go @@ -17,6 +17,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake" "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake/internal/exported" azexported "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" + fakepoller "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/fake" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" ) @@ -219,3 +220,11 @@ func PollerResponderMore[T any](p *fake.PollerResponder[T]) bool { func PollerResponderNext[T any](p *fake.PollerResponder[T], req *http.Request) (*http.Response, error) { return (*exported.PollerResponder[T])(p).Next(req) } + +// SanitizePagerPollerPath removes any fake-appended suffix from a URL's path. +// This function is called by the fake server internals. +func SanitizePagerPollerPath(path string) string { + path = exported.SanitizePagerPath(path) + path = fakepoller.SanitizePollerPath(path) + return path +} diff --git a/sdk/azcore/fake/server/server_test.go b/sdk/azcore/fake/server/server_test.go index 96656574e7b3..07138573ca86 100644 --- a/sdk/azcore/fake/server/server_test.go +++ b/sdk/azcore/fake/server/server_test.go @@ -268,6 +268,11 @@ func TestPollerResponderHelpers(t *testing.T) { require.Nil(t, resp) } +func TestSanitizePagerPollerPath(t *testing.T) { + const untouched = "/this/path/wont/change" + require.EqualValues(t, untouched, SanitizePagerPollerPath(untouched)) +} + type readFailer struct { wrapped io.ReadCloser } diff --git a/sdk/azcore/internal/pollers/fake/fake.go b/sdk/azcore/internal/pollers/fake/fake.go index 15adbee29f09..25983471867b 100644 --- a/sdk/azcore/internal/pollers/fake/fake.go +++ b/sdk/azcore/internal/pollers/fake/fake.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "net/http" + "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log" @@ -46,6 +47,9 @@ type Poller[T any] struct { FakeStatus string `json:"status"` } +// lroStatusURLSuffix is the URL path suffix for a faked LRO. +const lroStatusURLSuffix = "/get/fake/status" + // New creates a new Poller from the provided initial response. // Pass nil for response to create an empty Poller for rehydration. func New[T any](pl exported.Pipeline, resp *http.Response) (*Poller[T], error) { @@ -70,11 +74,17 @@ func New[T any](pl exported.Pipeline, resp *http.Response) (*Poller[T], error) { return nil, fmt.Errorf("expected string for CtxAPINameKey, the type was %T", ctxVal) } + qp := "" + if resp.Request.URL.RawQuery != "" { + qp = "?" + resp.Request.URL.RawQuery + } + p := &Poller[T]{ - pl: pl, - resp: resp, - APIName: apiName, - FakeURL: fmt.Sprintf("%s://%s%s/get/fake/status", resp.Request.URL.Scheme, resp.Request.URL.Host, resp.Request.URL.Path), + pl: pl, + resp: resp, + APIName: apiName, + // NOTE: any changes to this path format MUST be reflected in SanitizePollerPath() + FakeURL: fmt.Sprintf("%s://%s%s%s%s", resp.Request.URL.Scheme, resp.Request.URL.Host, resp.Request.URL.Path, lroStatusURLSuffix, qp), FakeStatus: fakeStatus, } return p, nil @@ -116,3 +126,8 @@ func (p *Poller[T]) Result(ctx context.Context, out *T) error { return pollers.ResultHelper(p.resp, poller.Failed(p.FakeStatus), out) } + +// SanitizePollerPath removes any fake-appended suffix from a URL's path. +func SanitizePollerPath(path string) string { + return strings.TrimSuffix(path, lroStatusURLSuffix) +} diff --git a/sdk/azcore/internal/pollers/fake/fake_test.go b/sdk/azcore/internal/pollers/fake/fake_test.go index 0a32d6dd3a86..5828371a2bcf 100644 --- a/sdk/azcore/internal/pollers/fake/fake_test.go +++ b/sdk/azcore/internal/pollers/fake/fake_test.go @@ -109,6 +109,10 @@ func TestPollSucceeded(t *testing.T) { })), resp) require.NoError(t, err) require.False(t, poller.Done()) + sanitizedPollerPath := SanitizePollerPath(poller.FakeURL) + require.NotEqualValues(t, sanitizedPollerPath, poller.FakeStatus) + require.EqualValues(t, fakeResourceURL, sanitizedPollerPath) + require.True(t, strings.HasPrefix(poller.FakeURL, sanitizedPollerPath)) resp, err = poller.Poll(pollCtx) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode)