Repository: Azure/azure-sdk-for-go
Pull request #20658 (2023-04-18 20:46:46)

Fix issue where tags header is passed if map is empty
<!--
Thank you for contributing to the Azure SDK for Go.

Please verify the following before submitting your PR, thank you!
-->

HNS accounts do not currently support tags. If a customer passes nil OR and empty map, we should not set the x-ms-tags header.

- [x] The purpose of this PR is explained in this or a referenced issue.
- [x] The PR does not update generated files.
   - These files are managed by the codegen framework at [Azure/autorest.go][].
- [x] Tests are included and/or updated for code changes.
- [x] Updates to [CHANGELOG.md][] are included.
- [x] 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/eng/common/scripts/TypeSpec-Project-Sync.ps1 b/eng/common/scripts/TypeSpec-Project-Sync.ps1
index f8656bc1d6..0cf2700e68 100644
--- a/eng/common/scripts/TypeSpec-Project-Sync.ps1
+++ b/eng/common/scripts/TypeSpec-Project-Sync.ps1
@@ -105,8 +105,8 @@ if ( $configuration["repo"] -and $configuration["commit"]) {
     try {
         if (!(Test-Path ".git")) {
             InitializeSparseGitClone $gitRemoteValue
-            UpdateSparseCheckoutFile $specSubDirectory $configuration["additionalDirectories"]
         }
+        UpdateSparseCheckoutFile $specSubDirectory $configuration["additionalDirectories"]
         git checkout $configuration["commit"]
         if ($LASTEXITCODE) { exit $LASTEXITCODE }
     }
diff --git a/eng/tools/generator/cmd/v2/common/changelogProcessor.go b/eng/tools/generator/cmd/v2/common/changelogProcessor.go
index 49d6541e06..37c1764e3b 100644
--- a/eng/tools/generator/cmd/v2/common/changelogProcessor.go
+++ b/eng/tools/generator/cmd/v2/common/changelogProcessor.go
@@ -18,6 +18,7 @@ import (
 	"github.com/Azure/azure-sdk-for-go/eng/tools/generator/repo"
 	"github.com/Azure/azure-sdk-for-go/eng/tools/internal/delta"
 	"github.com/Azure/azure-sdk-for-go/eng/tools/internal/exports"
+	"github.com/Masterminds/semver"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
@@ -45,33 +46,33 @@ func GetAllVersionTags(rpName, namespaceName string) ([]string, error) {
 		return nil, err
 	}
 	var tags []string
+	var versions []string
+	versionTag := make(map[string]string)
 	for _, tag := range result {
 		tagName := tag["ref"].(string)
-		if strings.Contains(tagName, "sdk/resourcemanager/"+rpName+"/"+namespaceName) {
-			tags = append(tags, tag["ref"].(string))
+		if strings.Contains(tagName, "sdk/resourcemanager/"+rpName+"/"+namespaceName+"/v") {
+			m := regexp.MustCompile(semver.SemVerRegex).FindString(tagName)
+			versions = append(versions, m)
+			versionTag[m] = tagName
 		}
 	}
-	sort.Sort(releaseTagsSort(tags))
 
-	return tags, nil
-}
-
-type releaseTagsSort []string
-
-func (t releaseTagsSort) Len() int {
-	return len(t)
-}
+	vs := make([]*semver.Version, len(versions))
+	for i, r := range versions {
+		v, err := semver.NewVersion(r)
+		if err != nil {
+			return nil, err
+		}
 
-func (t releaseTagsSort) Swap(i, j int) {
-	t[i], t[j] = t[j], t[i]
-}
+		vs[i] = v
+	}
+	sort.Sort(sort.Reverse(semver.Collection(vs)))
 
-func (t releaseTagsSort) Less(i, j int) bool {
-	if t[i] > t[j] {
-		return !strings.Contains(t[i], t[j])
-	} else {
-		return strings.Contains(t[j], t[i])
+	for _, v := range vs {
+		tags = append(tags, versionTag[v.Original()])
 	}
+
+	return tags, nil
 }
 
 func ContainsPreviewAPIVersion(packagePath string) (bool, error) {
diff --git a/eng/tools/generator/cmd/v2/common/fileProcessor.go b/eng/tools/generator/cmd/v2/common/fileProcessor.go
index 05eaa57226..c6f9114dc0 100644
--- a/eng/tools/generator/cmd/v2/common/fileProcessor.go
+++ b/eng/tools/generator/cmd/v2/common/fileProcessor.go
@@ -31,13 +31,13 @@ const (
 type PullRequestLabel string
 
 const (
-	StableLabel                    PullRequestLabel = "stable"
-	BetaLabel                      PullRequestLabel = "beta"
-	FirstStableLabel               PullRequestLabel = "first stable"
-	FirstStableBreakingChangeLabel PullRequestLabel = "first stable,breaking-change"
-	FirstBetaLabel                 PullRequestLabel = "first beta"
-	StableBreakingChangeLabel      PullRequestLabel = "stable,breaking-change"
-	BetaBreakingChangeLabel        PullRequestLabel = "beta,breaking-change"
+	StableLabel                PullRequestLabel = "Stable"
+	BetaLabel                  PullRequestLabel = "Beta"
+	FirstGALabel               PullRequestLabel = "FirstGA"
+	FirstGABreakingChangeLabel PullRequestLabel = "FirstGA,BreakingChange"
+	FirstBetaLabel             PullRequestLabel = "FirstBeta"
+	StableBreakingChangeLabel  PullRequestLabel = "Stable,BreakingChange"
+	BetaBreakingChangeLabel    PullRequestLabel = "Beta,BreakingChange"
 )
 
 var (
@@ -277,9 +277,9 @@ func CalculateNewVersion(changelog *model.Changelog, previousVersion string, isC
 			}
 			newVersion = *tempVersion
 			if changelog.HasBreakingChanges() {
-				prl = FirstStableBreakingChangeLabel
+				prl = FirstGABreakingChangeLabel
 			} else {
-				prl = FirstStableLabel
+				prl = FirstGALabel
 			}
 		} else if changelog.HasBreakingChanges() || changelog.Modified.HasAdditiveChanges() {
 			newVersion = version.IncMinor()
diff --git a/eng/tools/generator/cmd/v2/common/generation.go b/eng/tools/generator/cmd/v2/common/generation.go
index 8683c511b0..36e006fc3f 100644
--- a/eng/tools/generator/cmd/v2/common/generation.go
+++ b/eng/tools/generator/cmd/v2/common/generation.go
@@ -260,7 +260,7 @@ func (ctx *GenerateContext) GenerateForSingleRPNamespace(generateParam *Generate
 			if err = ReplaceVersion(packagePath, version.String()); err != nil {
 				return nil, err
 			}
-			prl = FirstStableLabel
+			prl = FirstGALabel
 		}
 
 		return &GenerateResult{
diff --git a/eng/tools/generator/cmd/v2/release/releaseCmd.go b/eng/tools/generator/cmd/v2/release/releaseCmd.go
index f79cc646a9..b07eabb98b 100644
--- a/eng/tools/generator/cmd/v2/release/releaseCmd.go
+++ b/eng/tools/generator/cmd/v2/release/releaseCmd.go
@@ -275,7 +275,7 @@ func (c *commandContext) generateFromRequest(sdkRepo repo.SDKRepository, specRep
 			}
 
 			log.Printf("Add Labels...\n")
-			err = common.ExecuteAddIssueLabels(sdkRepo.Root(), link.SpecOwner, link.ReleaseIssueRepo, issueNumber[len(issueNumber)-1], c.flags.Token, []string{"PRready"})
+			err = common.ExecuteAddIssueLabels(sdkRepo.Root(), link.SpecOwner, link.ReleaseIssueRepo, issueNumber[len(issueNumber)-1], c.flags.Token, []string{"PRready", issue.pullRequestLabel})
 			if err != nil {
 				return err
 			}
diff --git a/sdk/containers/azcontainerregistry/CHANGELOG.md b/sdk/containers/azcontainerregistry/CHANGELOG.md
index 5b9d6ef36b..f4d48377e5 100644
--- a/sdk/containers/azcontainerregistry/CHANGELOG.md
+++ b/sdk/containers/azcontainerregistry/CHANGELOG.md
@@ -7,6 +7,7 @@
 ### Breaking Changes
 
 ### Bugs Fixed
+* Add state restore for hash calculator when upload fails
 
 ### Other Changes
 
diff --git a/sdk/containers/azcontainerregistry/assets.json b/sdk/containers/azcontainerregistry/assets.json
index 2a0751ee59..ab37fdc6ee 100644
--- a/sdk/containers/azcontainerregistry/assets.json
+++ b/sdk/containers/azcontainerregistry/assets.json
@@ -2,5 +2,5 @@
   "AssetsRepo": "Azure/azure-sdk-assets",
   "AssetsRepoPrefixPath": "go",
   "TagPrefix": "go/containers/azcontainerregistry",
-  "Tag": "go/containers/azcontainerregistry_4e940b1981"
+  "Tag": "go/containers/azcontainerregistry_8eb5fc7e46"
 }
diff --git a/sdk/containers/azcontainerregistry/blob_custom_client.go b/sdk/containers/azcontainerregistry/blob_custom_client.go
index e1a0fc3501..8ad26ec54b 100644
--- a/sdk/containers/azcontainerregistry/blob_custom_client.go
+++ b/sdk/containers/azcontainerregistry/blob_custom_client.go
@@ -9,6 +9,7 @@ package azcontainerregistry
 import (
 	"context"
 	"crypto/sha256"
+	"encoding"
 	"errors"
 	"fmt"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -64,7 +65,8 @@ func NewBlobClient(endpoint string, credential azcore.TokenCredential, options *
 // BlobDigestCalculator help to calculate blob digest when uploading blob.
 // Don't use this type directly, use NewBlobDigestCalculator() instead.
 type BlobDigestCalculator struct {
-	h hash.Hash
+	h         hash.Hash
+	hashState []byte
 }
 
 type wrappedReadSeeker struct {
@@ -79,6 +81,17 @@ func NewBlobDigestCalculator() *BlobDigestCalculator {
 	}
 }
 
+func (b *BlobDigestCalculator) saveState() {
+	b.hashState, _ = b.h.(encoding.BinaryMarshaler).MarshalBinary()
+}
+
+func (b *BlobDigestCalculator) restoreState() {
+	if b.hashState == nil {
+		return
+	}
+	_ = b.h.(encoding.BinaryUnmarshaler).UnmarshalBinary(b.hashState)
+}
+
 // BlobClientUploadChunkOptions contains the optional parameters for the BlobClient.UploadChunk method.
 type BlobClientUploadChunkOptions struct {
 	// Start of range for the blob to be uploaded.
@@ -94,12 +107,17 @@ type BlobClientUploadChunkOptions struct {
 //   - blobDigestCalculator - Calculator that help to calculate blob digest
 //   - options - BlobClientUploadChunkOptions contains the optional parameters for the BlobClient.UploadChunk method.
 func (client *BlobClient) UploadChunk(ctx context.Context, location string, chunkData io.ReadSeeker, blobDigestCalculator *BlobDigestCalculator, options *BlobClientUploadChunkOptions) (BlobClientUploadChunkResponse, error) {
+	blobDigestCalculator.saveState()
 	wrappedChunkData := &wrappedReadSeeker{Reader: io.TeeReader(chunkData, blobDigestCalculator.h), Seeker: chunkData}
 	var requestOptions *blobClientUploadChunkOptions
 	if options != nil && options.RangeStart != nil && options.RangeEnd != nil {
 		requestOptions = &blobClientUploadChunkOptions{ContentRange: to.Ptr(fmt.Sprintf("%d-%d", *options.RangeStart, *options.RangeEnd))}
 	}
-	return client.uploadChunk(ctx, location, streaming.NopCloser(wrappedChunkData), requestOptions)
+	resp, err := client.uploadChunk(ctx, location, streaming.NopCloser(wrappedChunkData), requestOptions)
+	if err != nil {
+		blobDigestCalculator.restoreState()
+	}
+	return resp, err
 }
 
 // CompleteUpload - Complete the upload with previously uploaded content.
diff --git a/sdk/data/azcosmos/CHANGELOG.md b/sdk/data/azcosmos/CHANGELOG.md
index a93833bb77..fafec173a7 100644
--- a/sdk/data/azcosmos/CHANGELOG.md
+++ b/sdk/data/azcosmos/CHANGELOG.md
@@ -7,6 +7,7 @@
 ### Breaking Changes
 
 ### Bugs Fixed
+* Fixed unmarshalling error when using projections in value queries
 
 ### Other Changes
 
diff --git a/sdk/data/azcosmos/cosmos_query_response.go b/sdk/data/azcosmos/cosmos_query_response.go
index 2cd4e16362..53db1321fe 100644
--- a/sdk/data/azcosmos/cosmos_query_response.go
+++ b/sdk/data/azcosmos/cosmos_query_response.go
@@ -58,7 +58,7 @@ func newQueryResponse(resp *http.Response) (QueryItemsResponse, error) {
 }
 
 type queryServiceResponse struct {
-	Documents []map[string]interface{} `json:"Documents,omitempty"`
+	Documents []any `json:"Documents,omitempty"`
 }
 
 // QueryContainersResponse contains response from the container query operation.
diff --git a/sdk/storage/azblob/CHANGELOG.md b/sdk/storage/azblob/CHANGELOG.md
index a82db6dfbe..d8f4d29904 100644
--- a/sdk/storage/azblob/CHANGELOG.md
+++ b/sdk/storage/azblob/CHANGELOG.md
@@ -15,6 +15,7 @@
 ### Bugs Fixed
 
 * Fixed time formatting for the conditional request headers. Fixes [#20475](https://github.com/Azure/azure-sdk-for-go/issues/20475).
+* Fixed an issue where passing a blob tags map of length 0 would result in the x-ms-tags header to be sent to the service with an empty string as value.
 
 ### Other Changes
 
diff --git a/sdk/storage/azblob/internal/shared/shared.go b/sdk/storage/azblob/internal/shared/shared.go
index c473da2248..2244ae5efd 100644
--- a/sdk/storage/azblob/internal/shared/shared.go
+++ b/sdk/storage/azblob/internal/shared/shared.go
@@ -130,7 +130,7 @@ func ParseConnectionString(connectionString string) (ParsedConnectionString, err
 
 // SerializeBlobTags converts tags to generated.BlobTags
 func SerializeBlobTags(tagsMap map[string]string) *generated.BlobTags {
-	if tagsMap == nil {
+	if len(tagsMap) == 0 {
 		return nil
 	}
 	blobTagSet := make([]*generated.BlobTag, 0)
@@ -142,7 +142,7 @@ func SerializeBlobTags(tagsMap map[string]string) *generated.BlobTags {
 }
 
 func SerializeBlobTagsToStrPtr(tagsMap map[string]string) *string {
-	if tagsMap == nil {
+	if len(tagsMap) == 0 {
 		return nil
 	}
 	tags := make([]string, 0)


Test patch:
diff --git a/eng/tools/generator/cmd/v2/common/fileProcessor_test.go b/eng/tools/generator/cmd/v2/common/fileProcessor_test.go
index c9443e7454..b859f5daec 100644
--- a/eng/tools/generator/cmd/v2/common/fileProcessor_test.go
+++ b/eng/tools/generator/cmd/v2/common/fileProcessor_test.go
@@ -23,7 +23,7 @@ func TestCalculateNewVersion(t *testing.T) {
 	newVersion, prl, err := CalculateNewVersion(fixChange, "0.5.0", false)
 	assert.NoError(t, err)
 	assert.Equal(t, newVersion.String(), "1.0.0")
-	assert.Equal(t, FirstStableLabel, prl)
+	assert.Equal(t, FirstGALabel, prl)
 
 	// fix with beat
 	newVersion, prl, err = CalculateNewVersion(fixChange, "0.5.0", true)
@@ -35,7 +35,7 @@ func TestCalculateNewVersion(t *testing.T) {
 	newVersion, prl, err = CalculateNewVersion(breakingChange, "0.5.0", false)
 	assert.NoError(t, err)
 	assert.Equal(t, newVersion.String(), "1.0.0")
-	assert.Equal(t, FirstStableBreakingChangeLabel, prl)
+	assert.Equal(t, FirstGABreakingChangeLabel, prl)
 
 	// breaking with beta
 	newVersion, prl, err = CalculateNewVersion(breakingChange, "0.5.0", true)
@@ -47,7 +47,7 @@ func TestCalculateNewVersion(t *testing.T) {
 	newVersion, prl, err = CalculateNewVersion(additiveChange, "0.5.0", false)
 	assert.NoError(t, err)
 	assert.Equal(t, newVersion.String(), "1.0.0")
-	assert.Equal(t, FirstStableLabel, prl)
+	assert.Equal(t, FirstGALabel, prl)
 
 	// additive with beta
 	newVersion, prl, err = CalculateNewVersion(additiveChange, "0.5.0", true)
diff --git a/sdk/containers/azcontainerregistry/blob_custom_client_test.go b/sdk/containers/azcontainerregistry/blob_custom_client_test.go
index 7f31683092..5f97158f77 100644
--- a/sdk/containers/azcontainerregistry/blob_custom_client_test.go
+++ b/sdk/containers/azcontainerregistry/blob_custom_client_test.go
@@ -97,3 +97,49 @@ func TestNewBlobClient(t *testing.T) {
 	_, err = NewBlobClient("test", nil, &BlobClientOptions{ClientOptions: azcore.ClientOptions{Cloud: wrongCloudConfig}})
 	require.Errorf(t, err, "provided Cloud field is missing Azure Container Registry configuration")
 }
+
+func TestBlobDigestCalculator_saveAndRestoreState(t *testing.T) {
+	calculator := NewBlobDigestCalculator()
+	calculator.saveState()
+	calculator.restoreState()
+	calculator.h.Write([]byte("test1"))
+	sum := calculator.h.Sum(nil)
+	calculator.saveState()
+	calculator.h.Write([]byte("test2"))
+	require.NotEqual(t, sum, calculator.h.Sum(nil))
+	calculator.restoreState()
+	require.Equal(t, sum, calculator.h.Sum(nil))
+}
+
+func TestBlobClient_CompleteUpload_uploadByChunkFailOver(t *testing.T) {
+	startRecording(t)
+	endpoint, cred, options := getEndpointCredAndClientOptions(t)
+	ctx := context.Background()
+	client, err := NewBlobClient(endpoint, cred, &BlobClientOptions{ClientOptions: options})
+	require.NoError(t, err)
+	digest := "sha256:042a816809aac8d0f7d7cacac7965782ee2ecac3f21bcf9f24b1de1a7387b769"
+	getRes, err := client.GetBlob(ctx, "alpine", digest, nil)
+	require.NoError(t, err)
+	blob, err := io.ReadAll(getRes.BlobData)
+	require.NoError(t, err)
+	startRes, err := client.StartUpload(ctx, "hello-world", nil)
+	require.NoError(t, err)
+	calculator := NewBlobDigestCalculator()
+	oriReader := bytes.NewReader(blob)
+	firstPart := io.NewSectionReader(oriReader, int64(0), int64(len(blob)/2))
+	secondPart := io.NewSectionReader(oriReader, int64(len(blob)/2), int64(len(blob)-len(blob)/2))
+	uploadResp, err := client.UploadChunk(ctx, *startRes.Location, firstPart, calculator, &BlobClientUploadChunkOptions{RangeStart: to.Ptr(int32(0)), RangeEnd: to.Ptr(int32(len(blob)/2 - 1))})
+	require.NoError(t, err)
+	require.NotEmpty(t, *uploadResp.Location)
+	sum := calculator.h.Sum(nil)
+	// upload with a wrong range to test fail over
+	_, err = client.UploadChunk(ctx, *uploadResp.Location, secondPart, calculator, &BlobClientUploadChunkOptions{RangeStart: to.Ptr(int32(-1)), RangeEnd: to.Ptr(int32(-1))})
+	require.Error(t, err)
+	require.Equal(t, sum, calculator.h.Sum(nil))
+	uploadResp, err = client.UploadChunk(ctx, *uploadResp.Location, secondPart, calculator, &BlobClientUploadChunkOptions{RangeStart: to.Ptr(int32(len(blob) / 2)), RangeEnd: to.Ptr(int32(len(blob) - 1))})
+	require.NoError(t, err)
+	require.NotEmpty(t, *uploadResp.Location)
+	completeResp, err := client.CompleteUpload(ctx, *uploadResp.Location, calculator, nil)
+	require.NoError(t, err)
+	require.NotEmpty(t, *completeResp.DockerContentDigest)
+}
diff --git a/sdk/data/azcosmos/cosmos_query_response_test.go b/sdk/data/azcosmos/cosmos_query_response_test.go
index 1bb0e95900..db8d70350d 100644
--- a/sdk/data/azcosmos/cosmos_query_response_test.go
+++ b/sdk/data/azcosmos/cosmos_query_response_test.go
@@ -95,6 +95,79 @@ func TestQueryResponseParsing(t *testing.T) {
 	}
 }
 
+func TestQueryResponseValueParsing(t *testing.T) {
+	queryResponseRaw := map[string][]string{
+		"Documents": {"id1", "id2"},
+	}
+
+	jsonString, err := json.Marshal(queryResponseRaw)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	srv, close := mock.NewTLSServer()
+	defer close()
+	srv.SetResponse(
+		mock.WithBody(jsonString),
+		mock.WithHeader(cosmosHeaderEtag, "someEtag"),
+		mock.WithHeader(cosmosHeaderQueryMetrics, "someQueryMetrics"),
+		mock.WithHeader(cosmosHeaderIndexUtilization, "indexUtilization"),
+		mock.WithHeader(cosmosHeaderActivityId, "someActivityId"),
+		mock.WithHeader(cosmosHeaderRequestCharge, "13.42"))
+
+	req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", azruntime.PipelineOptions{}, &policy.ClientOptions{Transport: srv})
+	resp, _ := pl.Do(req)
+	parsedResponse, err := newQueryResponse(resp)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if parsedResponse.RawResponse == nil {
+		t.Fatal("parsedResponse.RawResponse is nil")
+	}
+
+	if parsedResponse.ActivityID != "someActivityId" {
+		t.Errorf("Expected ActivityId to be %s, but got %s", "someActivityId", parsedResponse.ActivityID)
+	}
+
+	if parsedResponse.RequestCharge != 13.42 {
+		t.Errorf("Expected RequestCharge to be %f, but got %f", 13.42, parsedResponse.RequestCharge)
+	}
+
+	if parsedResponse.ETag != "someEtag" {
+		t.Errorf("Expected ETag to be %s, but got %s", "someEtag", parsedResponse.ETag)
+	}
+
+	if *parsedResponse.QueryMetrics != "someQueryMetrics" {
+		t.Errorf("Expected IndexMetrics to be %s, but got %s", "someQueryMetrics", *parsedResponse.IndexMetrics)
+	}
+
+	if *parsedResponse.IndexMetrics != "indexUtilization" {
+		t.Errorf("Expected IndexUtilization to be %s, but got %s", "indexUtilization", *parsedResponse.IndexMetrics)
+	}
+
+	if len(parsedResponse.Items) != 2 {
+		t.Errorf("Expected 2 documents, but got %d", len(parsedResponse.Items))
+	}
+
+	for index, item := range parsedResponse.Items {
+		var itemResponseBody string
+		err = json.Unmarshal(item, &itemResponseBody)
+		if err != nil {
+			t.Fatalf("Failed to unmarshal item response: %v", err)
+		}
+
+		if itemResponseBody != ("id" + strconv.Itoa(index+1)) {
+			t.Errorf("Expected id to be %s, but got %s", "id"+strconv.Itoa(index+1), itemResponseBody)
+		}
+	}
+}
+
 func TestQueryContainersResponseParsing(t *testing.T) {
 	queryResponseRaw := map[string][]map[string]string{
 		"DocumentCollections": {
diff --git a/sdk/data/azcosmos/emulator_cosmos_query_test.go b/sdk/data/azcosmos/emulator_cosmos_query_test.go
index 7b669ba0e6..aec91fc305 100644
--- a/sdk/data/azcosmos/emulator_cosmos_query_test.go
+++ b/sdk/data/azcosmos/emulator_cosmos_query_test.go
@@ -219,6 +219,92 @@ func TestSinglePartitionQueryWithParameters(t *testing.T) {
 	}
 }
 
+func TestSinglePartitionQueryWithProjection(t *testing.T) {
+	emulatorTests := newEmulatorTests(t)
+	client := emulatorTests.getClient(t)
+
+	database := emulatorTests.createDatabase(t, context.TODO(), client, "queryTests")
+	defer emulatorTests.deleteDatabase(t, context.TODO(), database)
+	properties := ContainerProperties{
+		ID: "aContainer",
+		PartitionKeyDefinition: PartitionKeyDefinition{
+			Paths: []string{"/pk"},
+		},
+	}
+
+	_, err := database.CreateContainer(context.TODO(), properties, nil)
+	if err != nil {
+		t.Fatalf("Failed to create container: %v", err)
+	}
+
+	container, _ := database.NewContainer("aContainer")
+	documentsPerPk := 10
+	createSampleItems(t, container, documentsPerPk)
+
+	numberOfPages := 0
+	receivedIds := []string{}
+	opt := QueryOptions{PageSizeHint: 5}
+	queryPager := container.NewQueryItemsPager("select value c.id from c", NewPartitionKeyString("1"), &opt)
+	for queryPager.More() {
+		queryResponse, err := queryPager.NextPage(context.TODO())
+		if err != nil {
+			t.Fatalf("Failed to query items: %v", err)
+		}
+
+		numberOfPages++
+		for _, item := range queryResponse.Items {
+			var itemResponseBody string
+			err = json.Unmarshal(item, &itemResponseBody)
+			if err != nil {
+				t.Fatalf("Failed to unmarshal: %v", err)
+			}
+			receivedIds = append(receivedIds, itemResponseBody)
+		}
+
+		if queryPager.More() && queryResponse.ContinuationToken == "" {
+			t.Fatal("Query has more pages but no continuation was provided")
+		}
+
+		if queryResponse.QueryMetrics == nil {
+			t.Fatal("Query metrics were not returned")
+		}
+
+		if queryResponse.IndexMetrics != nil {
+			t.Fatal("Index metrics were returned")
+		}
+
+		if queryResponse.ActivityID == "" {
+			t.Fatal("Activity id was not returned")
+		}
+
+		if queryResponse.RequestCharge == 0 {
+			t.Fatal("Request charge was not returned")
+		}
+
+		if len(queryResponse.Items) != 5 && len(queryResponse.Items) != 0 {
+			t.Fatalf("Expected 5 items, got %d", len(queryResponse.Items))
+		}
+
+		if numberOfPages == 2 && opt.ContinuationToken != "" {
+			t.Fatalf("Original options should not be modified, initial continuation was empty, now it has %v", opt.ContinuationToken)
+		}
+	}
+
+	if numberOfPages != 2 {
+		t.Fatalf("Expected 2 pages, got %d", numberOfPages)
+	}
+
+	if len(receivedIds) != documentsPerPk {
+		t.Fatalf("Expected %d documents, got %d", documentsPerPk, len(receivedIds))
+	}
+
+	for i := 0; i < documentsPerPk; i++ {
+		if receivedIds[i] != strconv.Itoa(i) {
+			t.Fatalf("Expected id %d, got %s", i, receivedIds[i])
+		}
+	}
+}
+
 func createSampleItems(t *testing.T, container *ContainerClient, documentsPerPk int) {
 	for i := 0; i < documentsPerPk; i++ {
 		item := map[string]string{
diff --git a/sdk/storage/azblob/internal/shared/shared_test.go b/sdk/storage/azblob/internal/shared/shared_test.go
index b832a93eac..ea7f39cf2a 100644
--- a/sdk/storage/azblob/internal/shared/shared_test.go
+++ b/sdk/storage/azblob/internal/shared/shared_test.go
@@ -7,6 +7,7 @@
 package shared
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -94,3 +95,64 @@ func TestCParseConnectionStringAzurite(t *testing.T) {
 	require.Equal(t, "dummyaccountname", parsed.AccountName)
 	require.Equal(t, "secretkeykey", parsed.AccountKey)
 }
+
+func TestSerializeBlobTags(t *testing.T) {
+	var tags map[string]string
+
+	// Case 1
+	tags = nil
+	blobTags := SerializeBlobTags(tags)
+	require.Nil(t, blobTags)
+
+	// Case 2
+	tags = map[string]string{}
+	blobTags = SerializeBlobTags(tags)
+	require.Nil(t, blobTags)
+
+	// Case 3
+	tags = map[string]string{
+		"foo": "bar",
+		"az":  "sdk",
+		"sdk": "storage",
+	}
+	blobTags = SerializeBlobTags(tags)
+	require.NotNil(t, blobTags)
+	for _, tagPtr := range (*blobTags).BlobTagSet {
+		require.Contains(t, tags, *tagPtr.Key)
+		require.Equal(t, tags[*tagPtr.Key], *tagPtr.Value)
+		delete(tags, *tagPtr.Key)
+	}
+	require.Len(t, tags, 0)
+}
+
+func TestSerializeBlobTagsToStrPtr(t *testing.T) {
+	var tags map[string]string
+
+	// Case 1
+	tags = nil
+	tagsStr := SerializeBlobTagsToStrPtr(tags)
+	require.Nil(t, tagsStr)
+
+	// Case 2
+	tags = map[string]string{}
+	tagsStr = SerializeBlobTagsToStrPtr(tags)
+	require.Nil(t, tagsStr)
+
+	// Case 3
+	tags = map[string]string{
+		"foo": "bar",
+		"az":  "sdk",
+		"sdk": "storage",
+	}
+	tagsStr = SerializeBlobTagsToStrPtr(tags)
+	require.NotNil(t, tagsStr)
+	// split string on &
+	kvPairs := strings.Split(*tagsStr, "&")
+	for _, kv := range kvPairs {
+		pair := strings.Split(kv, "=")
+		require.Contains(t, tags, pair[0])
+		require.Equal(t, tags[pair[0]], pair[1])
+		delete(tags, pair[0])
+	}
+	require.Len(t, tags, 0)
+}