Repository: Azure/azure-sdk-for-go
Pull request #19500 (2022-11-04 19:41:30)

Add basic HTTP and ARM clients
Respective clients and their constructors added to the azcore and arm packages.
Refactored some type definitions to avoid circular dependencies.

<!--
Thank you for contributing to the Azure SDK for Go.

Please verify the following before submitting your PR, thank you!
-->

- [ ] The purpose of this PR is explained in this or a referenced issue.
- [ ] The PR does not update generated files.
   - These files are managed by the codegen framework at [Azure/autorest.go][].
- [ ] Tests are included and/or updated for code changes.
- [ ] Updates to [CHANGELOG.md][] are included.
- [ ] MIT license headers are included in each file.

[Azure/autorest.go]: https://github.com/Azure/autorest.go
[CHANGELOG.md]: https://github.com/Azure/azure-sdk-for-go/blob/main/CHANGELOG.md



Patch:
diff --git a/sdk/azcore/CHANGELOG.md b/sdk/azcore/CHANGELOG.md
index ce6e34fe18..1aa67fa300 100644
--- a/sdk/azcore/CHANGELOG.md
+++ b/sdk/azcore/CHANGELOG.md
@@ -5,6 +5,7 @@
 ### Features Added
 * Added `BearerTokenOptions.AuthorizationHandler` to enable extending `runtime.BearerTokenPolicy`
   with custom authorization logic
+* Added `Client` types and matching constructors to the `azcore` and `arm` packages.  These represent a basic client for HTTP and ARM respectively.
 
 ### Breaking Changes
 
diff --git a/sdk/azcore/arm/client.go b/sdk/azcore/arm/client.go
new file mode 100644
index 0000000000..94d018d435
--- /dev/null
+++ b/sdk/azcore/arm/client.go
@@ -0,0 +1,77 @@
+//go:build go1.18
+// +build go1.18
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package arm
+
+import (
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy"
+	armruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing"
+)
+
+// ClientOptions contains configuration settings for a client's pipeline.
+type ClientOptions = armpolicy.ClientOptions
+
+// Client is a HTTP client for use with ARM endpoints.  It consists of an endpoint, pipeline, and tracing provider.
+type Client struct {
+	ep string
+	pl runtime.Pipeline
+	tr tracing.Tracer
+}
+
+// NewClient creates a new Client instance with the provided values.
+// This client is intended to be used with Azure Resource Manager endpoints.
+//   - clientName - the fully qualified name of the client ("package.Client"); this is used by the tracing provider when creating spans
+//   - moduleVersion - the version of the containing module; used by the telemetry policy
+//   - cred - the TokenCredential used to authenticate the request
+//   - options - optional client configurations; pass nil to accept the default values
+func NewClient(clientName, moduleVersion string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
+	pkg, err := shared.ExtractPackageName(clientName)
+	if err != nil {
+		return nil, err
+	}
+
+	if options == nil {
+		options = &ClientOptions{}
+	}
+
+	if !options.Telemetry.Disabled {
+		if err := shared.ValidateModVer(moduleVersion); err != nil {
+			return nil, err
+		}
+	}
+
+	ep := cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint
+	if c, ok := options.Cloud.Services[cloud.ResourceManager]; ok {
+		ep = c.Endpoint
+	}
+	pl, err := armruntime.NewPipeline(pkg, moduleVersion, cred, runtime.PipelineOptions{}, options)
+	if err != nil {
+		return nil, err
+	}
+
+	tr := options.TracingProvider.NewTracer(clientName, moduleVersion)
+	return &Client{ep: ep, pl: pl, tr: tr}, nil
+}
+
+// Endpoint returns the service's base URL for this client.
+func (c *Client) Endpoint() string {
+	return c.ep
+}
+
+// Pipeline returns the pipeline for this client.
+func (c *Client) Pipeline() runtime.Pipeline {
+	return c.pl
+}
+
+// Tracer returns the tracer for this client.
+func (c *Client) Tracer() tracing.Tracer {
+	return c.tr
+}
diff --git a/sdk/azcore/arm/client_options.go b/sdk/azcore/arm/client_options.go
deleted file mode 100644
index 3e0f22e570..0000000000
--- a/sdk/azcore/arm/client_options.go
+++ /dev/null
@@ -1,17 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package arm
-
-import "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
-
-// ClientOptions contains configuration settings for a client's pipeline.
-type ClientOptions struct {
-	policy.ClientOptions
-
-	// DisableRPRegistration disables the auto-RP registration policy. Defaults to false.
-	DisableRPRegistration bool
-}
diff --git a/sdk/azcore/arm/policy/policy.go b/sdk/azcore/arm/policy/policy.go
index ddf3470936..098aa69934 100644
--- a/sdk/azcore/arm/policy/policy.go
+++ b/sdk/azcore/arm/policy/policy.go
@@ -39,3 +39,11 @@ type RegistrationOptions struct {
 	// NOTE: Setting this to a small value might cause the policy to prematurely fail.
 	PollingDuration time.Duration
 }
+
+// ClientOptions contains configuration settings for a client's pipeline.
+type ClientOptions struct {
+	policy.ClientOptions
+
+	// DisableRPRegistration disables the auto-RP registration policy. Defaults to false.
+	DisableRPRegistration bool
+}
diff --git a/sdk/azcore/arm/runtime/pipeline.go b/sdk/azcore/arm/runtime/pipeline.go
index 556484c622..a2e897765d 100644
--- a/sdk/azcore/arm/runtime/pipeline.go
+++ b/sdk/azcore/arm/runtime/pipeline.go
@@ -11,7 +11,6 @@ import (
 	"reflect"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
 	armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
 	azpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
@@ -21,9 +20,9 @@ import (
 // NewPipeline creates a pipeline from connection options. Policies from ClientOptions are
 // placed after policies from PipelineOptions. The telemetry policy, when enabled, will
 // use the specified module and version info.
-func NewPipeline(module, version string, cred azcore.TokenCredential, plOpts azruntime.PipelineOptions, options *arm.ClientOptions) (azruntime.Pipeline, error) {
+func NewPipeline(module, version string, cred azcore.TokenCredential, plOpts azruntime.PipelineOptions, options *armpolicy.ClientOptions) (azruntime.Pipeline, error) {
 	if options == nil {
-		options = &arm.ClientOptions{}
+		options = &armpolicy.ClientOptions{}
 	}
 	conf, err := getConfiguration(&options.ClientOptions)
 	if err != nil {
diff --git a/sdk/azcore/arm/arm.go b/sdk/azcore/arm/runtime/runtime.go
similarity index 97%
rename from sdk/azcore/arm/arm.go
rename to sdk/azcore/arm/runtime/runtime.go
index 3a0cb64429..1400d43799 100644
--- a/sdk/azcore/arm/arm.go
+++ b/sdk/azcore/arm/runtime/runtime.go
@@ -4,7 +4,7 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-package arm
+package runtime
 
 import "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
 
diff --git a/sdk/azcore/core.go b/sdk/azcore/core.go
index f9fb23422d..72c2cf21ee 100644
--- a/sdk/azcore/core.go
+++ b/sdk/azcore/core.go
@@ -7,25 +7,20 @@
 package azcore
 
 import (
-	"context"
 	"reflect"
-	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing"
 )
 
 // AccessToken represents an Azure service bearer access token with expiry information.
-type AccessToken struct {
-	Token     string
-	ExpiresOn time.Time
-}
+type AccessToken = exported.AccessToken
 
 // TokenCredential represents a credential capable of providing an OAuth token.
-type TokenCredential interface {
-	// GetToken requests an access token for the specified set of scopes.
-	GetToken(ctx context.Context, options policy.TokenRequestOptions) (AccessToken, error)
-}
+type TokenCredential = exported.TokenCredential
 
 // holds sentinel values used to send nulls
 var nullables map[reflect.Type]interface{} = map[reflect.Type]interface{}{}
@@ -73,3 +68,46 @@ func IsNullValue[T any](v T) bool {
 
 // ClientOptions contains configuration settings for a client's pipeline.
 type ClientOptions = policy.ClientOptions
+
+// Client is a basic HTTP client.  It consists of a pipeline and tracing provider.
+type Client struct {
+	pl runtime.Pipeline
+	tr tracing.Tracer
+}
+
+// NewClient creates a new Client instance with the provided values.
+//   - clientName - the fully qualified name of the client ("package.Client"); this is used by the tracing provider when creating spans
+//   - moduleVersion - the semantic version of the containing module; used by the telemetry policy
+//   - plOpts - pipeline configuration options; can be the zero-value
+//   - options - optional client configurations; pass nil to accept the default values
+func NewClient(clientName, moduleVersion string, plOpts runtime.PipelineOptions, options *ClientOptions) (*Client, error) {
+	pkg, err := shared.ExtractPackageName(clientName)
+	if err != nil {
+		return nil, err
+	}
+
+	if options == nil {
+		options = &ClientOptions{}
+	}
+
+	if !options.Telemetry.Disabled {
+		if err := shared.ValidateModVer(moduleVersion); err != nil {
+			return nil, err
+		}
+	}
+
+	pl := runtime.NewPipeline(pkg, moduleVersion, plOpts, options)
+
+	tr := options.TracingProvider.NewTracer(clientName, moduleVersion)
+	return &Client{pl: pl, tr: tr}, nil
+}
+
+// Pipeline returns the pipeline for this client.
+func (c *Client) Pipeline() runtime.Pipeline {
+	return c.pl
+}
+
+// Tracer returns the tracer for this client.
+func (c *Client) Tracer() tracing.Tracer {
+	return c.tr
+}
diff --git a/sdk/azcore/internal/exported/exported.go b/sdk/azcore/internal/exported/exported.go
index 6e029d493c..2ffbc0e474 100644
--- a/sdk/azcore/internal/exported/exported.go
+++ b/sdk/azcore/internal/exported/exported.go
@@ -7,8 +7,10 @@
 package exported
 
 import (
+	"context"
 	"io"
 	"net/http"
+	"time"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 )
@@ -58,3 +60,24 @@ func Payload(resp *http.Response) ([]byte, error) {
 	resp.Body = shared.NewNopClosingBytesReader(bytesBody)
 	return bytesBody, nil
 }
+
+// AccessToken represents an Azure service bearer access token with expiry information.
+// Exported as azcore.AccessToken.
+type AccessToken struct {
+	Token     string
+	ExpiresOn time.Time
+}
+
+// TokenRequestOptions contain specific parameter that may be used by credentials types when attempting to get a token.
+// Exported as policy.TokenRequestOptions.
+type TokenRequestOptions struct {
+	// Scopes contains the list of permission scopes required for the token.
+	Scopes []string
+}
+
+// TokenCredential represents a credential capable of providing an OAuth token.
+// Exported as azcore.TokenCredential.
+type TokenCredential interface {
+	// GetToken requests an access token for the specified set of scopes.
+	GetToken(ctx context.Context, options TokenRequestOptions) (AccessToken, error)
+}
diff --git a/sdk/azcore/internal/shared/shared.go b/sdk/azcore/internal/shared/shared.go
index 96eef2956f..7c71df3070 100644
--- a/sdk/azcore/internal/shared/shared.go
+++ b/sdk/azcore/internal/shared/shared.go
@@ -9,10 +9,13 @@ package shared
 import (
 	"context"
 	"errors"
+	"fmt"
 	"io"
 	"net/http"
 	"reflect"
+	"regexp"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -133,3 +136,24 @@ type TransportFunc func(*http.Request) (*http.Response, error)
 func (pf TransportFunc) Do(req *http.Request) (*http.Response, error) {
 	return pf(req)
 }
+
+// ValidateModVer verifies that moduleVersion is a valid semver 2.0 string.
+func ValidateModVer(moduleVersion string) error {
+	modVerRegx := regexp.MustCompile(`^v\d+\.\d+\.\d+(?:-[a-zA-Z0-9_.-]+)?$`)
+	if !modVerRegx.MatchString(moduleVersion) {
+		return fmt.Errorf("malformed moduleVersion param value %s", moduleVersion)
+	}
+	return nil
+}
+
+// ExtractPackageName returns "package" from "package.Client".
+// If clientName is malformed, an error is returned.
+func ExtractPackageName(clientName string) (string, error) {
+	pkg, client, ok := strings.Cut(clientName, ".")
+	if !ok {
+		return "", fmt.Errorf("missing . in clientName %s", clientName)
+	} else if pkg == "" || client == "" {
+		return "", fmt.Errorf("malformed clientName %s", clientName)
+	}
+	return pkg, nil
+}
diff --git a/sdk/azcore/policy/policy.go b/sdk/azcore/policy/policy.go
index 83b46a95ba..c427e14d88 100644
--- a/sdk/azcore/policy/policy.go
+++ b/sdk/azcore/policy/policy.go
@@ -126,10 +126,7 @@ type TelemetryOptions struct {
 }
 
 // TokenRequestOptions contain specific parameter that may be used by credentials types when attempting to get a token.
-type TokenRequestOptions struct {
-	// Scopes contains the list of permission scopes required for the token.
-	Scopes []string
-}
+type TokenRequestOptions = exported.TokenRequestOptions
 
 // BearerTokenOptions configures the bearer token policy's behavior.
 type BearerTokenOptions struct {
diff --git a/sdk/azcore/runtime/policy_bearer_token.go b/sdk/azcore/runtime/policy_bearer_token.go
index 66d90f6351..b61e4c121f 100644
--- a/sdk/azcore/runtime/policy_bearer_token.go
+++ b/sdk/azcore/runtime/policy_bearer_token.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
@@ -18,10 +18,10 @@ import (
 // BearerTokenPolicy authorizes requests with bearer tokens acquired from a TokenCredential.
 type BearerTokenPolicy struct {
 	// mainResource is the resource to be retreived using the tenant specified in the credential
-	mainResource *temporal.Resource[azcore.AccessToken, acquiringResourceState]
+	mainResource *temporal.Resource[exported.AccessToken, acquiringResourceState]
 	// the following fields are read-only
 	authzHandler policy.AuthorizationHandler
-	cred         azcore.TokenCredential
+	cred         exported.TokenCredential
 	scopes       []string
 }
 
@@ -33,10 +33,10 @@ type acquiringResourceState struct {
 
 // acquire acquires or updates the resource; only one
 // thread/goroutine at a time ever calls this function
-func acquire(state acquiringResourceState) (newResource azcore.AccessToken, newExpiration time.Time, err error) {
+func acquire(state acquiringResourceState) (newResource exported.AccessToken, newExpiration time.Time, err error) {
 	tk, err := state.p.cred.GetToken(state.req.Raw().Context(), state.tro)
 	if err != nil {
-		return azcore.AccessToken{}, time.Time{}, err
+		return exported.AccessToken{}, time.Time{}, err
 	}
 	return tk, tk.ExpiresOn, nil
 }
@@ -45,7 +45,7 @@ func acquire(state acquiringResourceState) (newResource azcore.AccessToken, newE
 // cred: an azcore.TokenCredential implementation such as a credential object from azidentity
 // scopes: the list of permission scopes required for the token.
 // opts: optional settings. Pass nil to accept default values; this is the same as passing a zero-value options.
-func NewBearerTokenPolicy(cred azcore.TokenCredential, scopes []string, opts *policy.BearerTokenOptions) *BearerTokenPolicy {
+func NewBearerTokenPolicy(cred exported.TokenCredential, scopes []string, opts *policy.BearerTokenOptions) *BearerTokenPolicy {
 	if opts == nil {
 		opts = &policy.BearerTokenOptions{}
 	}


Test patch:
diff --git a/sdk/azcore/arm/client_test.go b/sdk/azcore/arm/client_test.go
new file mode 100644
index 0000000000..a95d0c4488
--- /dev/null
+++ b/sdk/azcore/arm/client_test.go
@@ -0,0 +1,70 @@
+//go:build go1.18
+// +build go1.18
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package arm
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/stretchr/testify/require"
+)
+
+type fakeCredential struct{}
+
+func (mc fakeCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
+	return azcore.AccessToken{Token: "***", ExpiresOn: time.Now().Add(time.Hour)}, nil
+}
+
+func TestNewClient(t *testing.T) {
+	client, err := NewClient("package.Client", "v1.0.0", fakeCredential{}, nil)
+	require.NoError(t, err)
+	require.NotNil(t, client)
+	require.Equal(t, cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint, client.Endpoint())
+	require.NotZero(t, client.Pipeline())
+	require.Zero(t, client.Tracer())
+
+	client, err = NewClient("package.Client", "", fakeCredential{}, &ClientOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: cloud.AzureChina,
+			Telemetry: policy.TelemetryOptions{
+				Disabled: true,
+			},
+		},
+	})
+	require.NoError(t, err)
+	require.NotNil(t, client)
+	require.Equal(t, cloud.AzureChina.Services[cloud.ResourceManager].Endpoint, client.Endpoint())
+}
+
+func TestNewClientError(t *testing.T) {
+	client, err := NewClient("malformed", "v1.0.0", fakeCredential{}, nil)
+	require.Error(t, err)
+	require.Nil(t, client)
+
+	client, err = NewClient("package.Client", "malformed", fakeCredential{}, nil)
+	require.Error(t, err)
+	require.Nil(t, client)
+
+	badCloud := cloud.Configuration{
+		Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
+			cloud.ResourceManager: {
+				Audience: "fake/audience",
+			},
+		},
+	}
+	client, err = NewClient("package.Client", "v1.0.0", fakeCredential{}, &ClientOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: badCloud,
+		},
+	})
+	require.Error(t, err)
+	require.Nil(t, client)
+}
diff --git a/sdk/azcore/arm/runtime/pipeline_test.go b/sdk/azcore/arm/runtime/pipeline_test.go
index 4c41531c8f..1fc6fef99a 100644
--- a/sdk/azcore/arm/runtime/pipeline_test.go
+++ b/sdk/azcore/arm/runtime/pipeline_test.go
@@ -15,7 +15,7 @@ import (
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
+	armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
@@ -30,7 +30,7 @@ func TestNewPipelineWithAPIVersion(t *testing.T) {
 	srv, close := mock.NewServer()
 	defer close()
 	srv.SetResponse()
-	pl, err := NewPipeline("...", "...", mockCredential{}, azruntime.PipelineOptions{}, &arm.ClientOptions{
+	pl, err := NewPipeline("...", "...", mockCredential{}, azruntime.PipelineOptions{}, &armpolicy.ClientOptions{
 		ClientOptions: policy.ClientOptions{
 			APIVersion: version,
 		},
@@ -47,7 +47,7 @@ func TestNewPipelineWithOptions(t *testing.T) {
 	srv, close := mock.NewServer()
 	defer close()
 	srv.AppendResponse()
-	opt := arm.ClientOptions{}
+	opt := armpolicy.ClientOptions{}
 	opt.Transport = srv
 	req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL())
 	if err != nil {
@@ -74,7 +74,7 @@ func TestNewPipelineWithCustomTelemetry(t *testing.T) {
 	srv, close := mock.NewServer()
 	defer close()
 	srv.AppendResponse()
-	opt := arm.ClientOptions{}
+	opt := armpolicy.ClientOptions{}
 	opt.Transport = srv
 	opt.Telemetry.ApplicationID = myTelemetry
 	if opt.Telemetry.ApplicationID != myTelemetry {
@@ -105,7 +105,7 @@ func TestDisableAutoRPRegistration(t *testing.T) {
 	defer close()
 	// initial response that RP is unregistered
 	srv.SetResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp)))
-	opts := &arm.ClientOptions{DisableRPRegistration: true, ClientOptions: policy.ClientOptions{Transport: srv}}
+	opts := &armpolicy.ClientOptions{DisableRPRegistration: true, ClientOptions: policy.ClientOptions{Transport: srv}}
 	req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL())
 	if err != nil {
 		t.Fatalf("Unexpected error: %v", err)
@@ -155,7 +155,7 @@ func TestPipelineWithCustomPolicies(t *testing.T) {
 	srv.AppendResponse(mock.WithStatusCode(http.StatusOK))
 	perCallPolicy := countingPolicy{}
 	perRetryPolicy := countingPolicy{}
-	opts := &arm.ClientOptions{
+	opts := &armpolicy.ClientOptions{
 		DisableRPRegistration: true,
 		ClientOptions: policy.ClientOptions{
 			PerCallPolicies:  []policy.Policy{&perCallPolicy},
@@ -192,7 +192,7 @@ func TestPipelineAudience(t *testing.T) {
 		srv, close := mock.NewServer()
 		defer close()
 		srv.AppendResponse(mock.WithStatusCode(200))
-		opts := &arm.ClientOptions{}
+		opts := &armpolicy.ClientOptions{}
 		opts.Cloud = c
 		opts.Transport = srv
 		audience := opts.Cloud.Services[cloud.ResourceManager].Audience
@@ -239,7 +239,7 @@ func TestPipelineWithIncompleteCloudConfig(t *testing.T) {
 		}},
 	}
 	for _, c := range partialConfigs {
-		opts := &arm.ClientOptions{}
+		opts := &armpolicy.ClientOptions{}
 		opts.Cloud = c
 		_, err := NewPipeline("test", "v0.1.0", mockCredential{}, azruntime.PipelineOptions{}, opts)
 		if err == nil {
diff --git a/sdk/azcore/arm/runtime/policy_bearer_token_test.go b/sdk/azcore/arm/runtime/policy_bearer_token_test.go
index 8e9657a41e..04de55b595 100644
--- a/sdk/azcore/arm/runtime/policy_bearer_token_test.go
+++ b/sdk/azcore/arm/runtime/policy_bearer_token_test.go
@@ -13,7 +13,6 @@ import (
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
 	armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	azpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
@@ -57,7 +56,7 @@ func defaultTestPipeline(srv azpolicy.Transporter, scope string) (runtime.Pipeli
 		"v0.1.0",
 		mockCredential{},
 		runtime.PipelineOptions{},
-		&arm.ClientOptions{
+		&armpolicy.ClientOptions{
 			ClientOptions: azpolicy.ClientOptions{
 				Retry:     retryOpts,
 				Transport: srv,
diff --git a/sdk/azcore/core_test.go b/sdk/azcore/core_test.go
index 56b8b55045..13d3361e1f 100644
--- a/sdk/azcore/core_test.go
+++ b/sdk/azcore/core_test.go
@@ -9,6 +9,10 @@ package azcore
 import (
 	"reflect"
 	"testing"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
+	"github.com/stretchr/testify/require"
 )
 
 func TestNullValue(t *testing.T) {
@@ -101,3 +105,29 @@ func TestIsNullValueMapSlice(t *testing.T) {
 		t.Fatal("expected null slice")
 	}
 }
+
+func TestNewClient(t *testing.T) {
+	client, err := NewClient("package.Client", "v1.0.0", runtime.PipelineOptions{}, nil)
+	require.NoError(t, err)
+	require.NotNil(t, client)
+	require.NotZero(t, client.Pipeline())
+	require.Zero(t, client.Tracer())
+
+	client, err = NewClient("package.Client", "", runtime.PipelineOptions{}, &ClientOptions{
+		Telemetry: policy.TelemetryOptions{
+			Disabled: true,
+		},
+	})
+	require.NoError(t, err)
+	require.NotNil(t, client)
+}
+
+func TestNewClientError(t *testing.T) {
+	client, err := NewClient("malformed", "v1.0.0", runtime.PipelineOptions{}, nil)
+	require.Error(t, err)
+	require.Nil(t, client)
+
+	client, err = NewClient("package.Client", "malformed", runtime.PipelineOptions{}, nil)
+	require.Error(t, err)
+	require.Nil(t, client)
+}
diff --git a/sdk/azcore/internal/pollers/loc/loc_test.go b/sdk/azcore/internal/pollers/loc/loc_test.go
index fb9e64b285..650ca5c8ab 100644
--- a/sdk/azcore/internal/pollers/loc/loc_test.go
+++ b/sdk/azcore/internal/pollers/loc/loc_test.go
@@ -14,7 +14,6 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
@@ -160,7 +159,7 @@ func TestUpdateFailedWithProvisioningState(t *testing.T) {
 	require.Equal(t, http.StatusOK, resp.StatusCode)
 	require.True(t, poller.Done())
 	err = poller.Result(context.Background(), nil)
-	var respErr *azcore.ResponseError
+	var respErr *exported.ResponseError
 	require.ErrorAs(t, err, &respErr)
 }
 
diff --git a/sdk/azcore/internal/shared/shared_test.go b/sdk/azcore/internal/shared/shared_test.go
index 20a7e73098..db8fbfc85d 100644
--- a/sdk/azcore/internal/shared/shared_test.go
+++ b/sdk/azcore/internal/shared/shared_test.go
@@ -140,3 +140,32 @@ func TestTransportFunc(t *testing.T) {
 	require.Nil(t, resp)
 	require.NoError(t, err)
 }
+
+func TestValidateModVer(t *testing.T) {
+	require.NoError(t, ValidateModVer("v1.2.3"))
+	require.NoError(t, ValidateModVer("v1.2.3-beta.1"))
+	require.Error(t, ValidateModVer("1.2.3"))
+	require.Error(t, ValidateModVer("v1.2"))
+}
+
+func TestExtractPackageName(t *testing.T) {
+	pkg, err := ExtractPackageName("package.Client")
+	require.NoError(t, err)
+	require.Equal(t, "package", pkg)
+
+	pkg, err = ExtractPackageName("malformed")
+	require.Error(t, err)
+	require.Empty(t, pkg)
+
+	pkg, err = ExtractPackageName(".malformed")
+	require.Error(t, err)
+	require.Empty(t, pkg)
+
+	pkg, err = ExtractPackageName("malformed.")
+	require.Error(t, err)
+	require.Empty(t, pkg)
+
+	pkg, err = ExtractPackageName("")
+	require.Error(t, err)
+	require.Empty(t, pkg)
+}
diff --git a/sdk/azcore/runtime/policy_bearer_token_test.go b/sdk/azcore/runtime/policy_bearer_token_test.go
index df476168fd..01ec151dd1 100644
--- a/sdk/azcore/runtime/policy_bearer_token_test.go
+++ b/sdk/azcore/runtime/policy_bearer_token_test.go
@@ -12,7 +12,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
@@ -28,14 +28,14 @@ const (
 )
 
 type mockCredential struct {
-	getTokenImpl func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error)
+	getTokenImpl func(ctx context.Context, options policy.TokenRequestOptions) (exported.AccessToken, error)
 }
 
-func (mc mockCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
+func (mc mockCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (exported.AccessToken, error) {
 	if mc.getTokenImpl != nil {
 		return mc.getTokenImpl(ctx, options)
 	}
-	return azcore.AccessToken{Token: "***", ExpiresOn: time.Now().Add(time.Hour)}, nil
+	return exported.AccessToken{Token: "***", ExpiresOn: time.Now().Add(time.Hour)}, nil
 }
 
 func (mc mockCredential) Do(req *policy.Request) (*http.Response, error) {
@@ -81,8 +81,8 @@ func TestBearerPolicy_CredentialFailGetToken(t *testing.T) {
 	defer close()
 	expectedErr := errors.New("oops")
 	failCredential := mockCredential{}
-	failCredential.getTokenImpl = func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
-		return azcore.AccessToken{}, expectedErr
+	failCredential.getTokenImpl = func(ctx context.Context, options policy.TokenRequestOptions) (exported.AccessToken, error) {
+		return exported.AccessToken{}, expectedErr
 	}
 	b := NewBearerTokenPolicy(failCredential, nil, nil)
 	pipeline := newTestPipeline(&policy.ClientOptions{