Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formalize contract for fakes sanitizing request URLs #21217

Merged
merged 1 commit into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sdk/azcore/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 18 additions & 1 deletion sdk/azcore/fake/internal/exported/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strconv"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions sdk/azcore/fake/internal/exported/fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"net/http"
"net/url"
"strings"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions sdk/azcore/fake/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}
5 changes: 5 additions & 0 deletions sdk/azcore/fake/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
23 changes: 19 additions & 4 deletions sdk/azcore/internal/pollers/fake/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
4 changes: 4 additions & 0 deletions sdk/azcore/internal/pollers/fake/fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down