From dfbda666b1255886f7e9ea4dd5be9c556e265692 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:40:36 +0000 Subject: [PATCH 1/2] Initial plan From 7b272cd09c30fc737a021ac8612787b2666c8b51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:49:37 +0000 Subject: [PATCH 2/2] Revert Directory.Build.props and CosmosItemTests whitespace changes Co-authored-by: kirankumarkolli <6880899+kirankumarkolli@users.noreply.github.com> --- Directory.Build.props | 2 +- .../src/Handler/RequestMessage.cs | 6 +- .../CosmosItemTests.cs | 5963 ++++++++--------- 3 files changed, 2985 insertions(+), 2986 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4bdeecd200..14db28f1fb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ 3.52.0 3.53.0 preview.0 - 3.40.1 + 3.39.1 1.0.0 beta.0 2.0.5 diff --git a/Microsoft.Azure.Cosmos/src/Handler/RequestMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/RequestMessage.cs index 1bc8ed5a80..1d05463702 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/RequestMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/RequestMessage.cs @@ -294,9 +294,9 @@ internal DocumentServiceRequest ToDocumentServiceRequest() } serviceRequest.UseStatusCodeForFailures = true; - serviceRequest.UseStatusCodeFor429 = true; - serviceRequest.UseStatusCodeFor4041002 = true; - serviceRequest.UseStatusCodeFor403 = true; + serviceRequest.UseStatusCodeFor429 = true; + serviceRequest.UseStatusCodeFor4041002 = true; + serviceRequest.UseStatusCodeFor403 = true; serviceRequest.UseStatusCodeForBadRequest = true; serviceRequest.Properties = this.Properties; this.DocumentServiceRequest = serviceRequest; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 11a4a61f2d..277c7b868b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -1,81 +1,81 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Json; - using Microsoft.Azure.Cosmos.Query.Core.ExecutionContext; - using Microsoft.Azure.Cosmos.Query.Core.QueryClient; - using Microsoft.Azure.Cosmos.Routing; - using Microsoft.Azure.Cosmos.Tracing; - using Microsoft.Azure.Documents; - using Microsoft.Azure.Cosmos; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using JsonReader = Json.JsonReader; - using JsonWriter = Json.JsonWriter; - using PartitionKey = Documents.PartitionKey; - using static Microsoft.Azure.Cosmos.SDK.EmulatorTests.TransportClientHelper; - using System.Reflection; - using System.Text.RegularExpressions; - using Microsoft.Azure.Cosmos.Diagnostics; - - [TestClass] - public class CosmosItemTests : BaseCosmosClientHelper - { - private Container Container = null; - private ContainerProperties containerSettings = null; - - private static readonly string nonPartitionItemId = "fixed-Container-Item"; - private static readonly string undefinedPartitionItemId = "undefined-partition-Item"; - - [TestInitialize] - public async Task TestInitialize() - { - await base.TestInit(validateSinglePartitionKeyRangeCacheCall: true); - string PartitionKey = "/pk"; - this.containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey); - ContainerResponse response = await this.database.CreateContainerAsync( - this.containerSettings, - throughput: 15000, - cancellationToken: this.cancellationToken); - Assert.IsNotNull(response); - Assert.IsNotNull(response.Container); - Assert.IsNotNull(response.Resource); - this.Container = response; - } - - [TestCleanup] - public async Task Cleanup() - { - await base.TestCleanup(); - } - - [TestMethod] - public void ParentResourceTest() - { - Assert.AreEqual(this.database, this.Container.Database); - Assert.AreEqual(this.GetClient(), this.Container.Database.Client); - } - +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Json; + using Microsoft.Azure.Cosmos.Query.Core.ExecutionContext; + using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Cosmos; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using JsonReader = Json.JsonReader; + using JsonWriter = Json.JsonWriter; + using PartitionKey = Documents.PartitionKey; + using static Microsoft.Azure.Cosmos.SDK.EmulatorTests.TransportClientHelper; + using System.Reflection; + using System.Text.RegularExpressions; + using Microsoft.Azure.Cosmos.Diagnostics; + + [TestClass] + public class CosmosItemTests : BaseCosmosClientHelper + { + private Container Container = null; + private ContainerProperties containerSettings = null; + + private static readonly string nonPartitionItemId = "fixed-Container-Item"; + private static readonly string undefinedPartitionItemId = "undefined-partition-Item"; + + [TestInitialize] + public async Task TestInitialize() + { + await base.TestInit(validateSinglePartitionKeyRangeCacheCall: true); + string PartitionKey = "/pk"; + this.containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey); + ContainerResponse response = await this.database.CreateContainerAsync( + this.containerSettings, + throughput: 15000, + cancellationToken: this.cancellationToken); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Container); + Assert.IsNotNull(response.Resource); + this.Container = response; + } + + [TestCleanup] + public async Task Cleanup() + { + await base.TestCleanup(); + } + + [TestMethod] + public void ParentResourceTest() + { + Assert.AreEqual(this.database, this.Container.Database); + Assert.AreEqual(this.GetClient(), this.Container.Database.Client); + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task CreateDropItemWithInvalidIdCharactersTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task CreateDropItemWithInvalidIdCharactersTest(bool binaryEncodingEnabledInClient) { try { @@ -137,12 +137,12 @@ await this.Container.ReadItemAsync( { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); } - } - + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task CreateDropItemTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task CreateDropItemTest(bool binaryEncodingEnabledInClient) { try { @@ -187,13 +187,13 @@ public async Task CreateDropItemTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task ClientConsistencyTestAsync(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task ClientConsistencyTestAsync(bool binaryEncodingEnabledInClient) { try { @@ -228,13 +228,13 @@ public async Task ClientConsistencyTestAsync(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task NegativeCreateItemTest(bool binaryEncodingEnabledInClient) + public async Task NegativeCreateItemTest(bool binaryEncodingEnabledInClient) { try { @@ -284,13 +284,13 @@ public async Task NegativeCreateItemTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task NegativeCreateDropItemTest(bool binaryEncodingEnabledInClient) + public async Task NegativeCreateDropItemTest(bool binaryEncodingEnabledInClient) { try { @@ -309,13 +309,13 @@ public async Task NegativeCreateDropItemTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task MemoryStreamBufferIsAccessibleOnResponse(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task MemoryStreamBufferIsAccessibleOnResponse(bool binaryEncodingEnabledInClient) { try { @@ -350,13 +350,13 @@ public async Task MemoryStreamBufferIsAccessibleOnResponse(bool binaryEncodingEn finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task CustomSerilizerTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task CustomSerilizerTest(bool binaryEncodingEnabledInClient) { try { @@ -410,13 +410,13 @@ public async Task CustomSerilizerTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task CreateDropItemUndefinedPartitionKeyTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task CreateDropItemUndefinedPartitionKeyTest(bool binaryEncodingEnabledInClient) { try { @@ -443,13 +443,13 @@ public async Task CreateDropItemUndefinedPartitionKeyTest(bool binaryEncodingEna finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task CreateDropItemPartitionKeyNotInTypeTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task CreateDropItemPartitionKeyNotInTypeTest(bool binaryEncodingEnabledInClient) { try { @@ -491,13 +491,13 @@ public async Task CreateDropItemPartitionKeyNotInTypeTest(bool binaryEncodingEna finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task CreateDropItemMultiPartPartitionKeyTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task CreateDropItemMultiPartPartitionKeyTest(bool binaryEncodingEnabledInClient) { try { @@ -547,26 +547,26 @@ public async Task CreateDropItemMultiPartPartitionKeyTest(bool binaryEncodingEna finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - - [TestMethod] - public async Task ReadCollectionNotExists() - { - string collectionName = Guid.NewGuid().ToString(); - Container testContainer = this.database.GetContainer(collectionName); - await CosmosItemTests.TestNonePKForNonExistingContainer(testContainer); - - // Item -> Container -> Database contract - string dbName = Guid.NewGuid().ToString(); - testContainer = this.GetClient().GetDatabase(dbName).GetContainer(collectionName); - await CosmosItemTests.TestNonePKForNonExistingContainer(testContainer); - } - + } + } + + [TestMethod] + public async Task ReadCollectionNotExists() + { + string collectionName = Guid.NewGuid().ToString(); + Container testContainer = this.database.GetContainer(collectionName); + await CosmosItemTests.TestNonePKForNonExistingContainer(testContainer); + + // Item -> Container -> Database contract + string dbName = Guid.NewGuid().ToString(); + testContainer = this.GetClient().GetDatabase(dbName).GetContainer(collectionName); + await CosmosItemTests.TestNonePKForNonExistingContainer(testContainer); + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task NonPartitionKeyLookupCacheTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task NonPartitionKeyLookupCacheTest(bool binaryEncodingEnabledInClient) { try { @@ -667,7 +667,7 @@ public async Task NonPartitionKeyLookupCacheTest(bool binaryEncodingEnabledInCli finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } + } } [TestMethod] @@ -750,11 +750,11 @@ public async Task HttpRequestVersionIsTwoPointZeroWhenUsingThinClientMode() } [TestMethod] - [DataRow(true, true, DisplayName = "Test scenario when binary encoding is enabled at client level and expected stream response type is binary.")] - [DataRow(true, false, DisplayName = "Test scenario when binary encoding is enabled at client level and expected stream response type is text.")] - [DataRow(false, true, DisplayName = "Test scenario when binary encoding is disabled at client level and expected stream response type is binary.")] - [DataRow(false, false, DisplayName = "Test scenario when binary encoding is disabled at client level and expected stream response type is text.")] - public async Task CreateDropItemStreamTest(bool binaryEncodingEnabledInClient, bool shouldExpectBinaryOnResponse) + [DataRow(true, true, DisplayName = "Test scenario when binary encoding is enabled at client level and expected stream response type is binary.")] + [DataRow(true, false, DisplayName = "Test scenario when binary encoding is enabled at client level and expected stream response type is text.")] + [DataRow(false, true, DisplayName = "Test scenario when binary encoding is disabled at client level and expected stream response type is binary.")] + [DataRow(false, false, DisplayName = "Test scenario when binary encoding is disabled at client level and expected stream response type is text.")] + public async Task CreateDropItemStreamTest(bool binaryEncodingEnabledInClient, bool shouldExpectBinaryOnResponse) { try { @@ -846,7 +846,7 @@ public async Task CreateDropItemStreamTest(bool binaryEncodingEnabledInClient, b finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } + } } [TestMethod] @@ -857,7 +857,7 @@ public async Task CreateDropItemStreamTest(bool binaryEncodingEnabledInClient, b [DataRow(false, false, DisplayName = "Test scenario when binary encoding is disabled at client level and stream conversation for binary encoding is enabled.")] public async Task CreateItemStream_WithEnableBinaryResponseOptions_ShouldSkipStreamConversation( bool binaryEncodingEnabledInClient, - bool enableStreamPassThrough) + bool enableStreamPassThrough) { Cosmos.Database database = null; Container container = null; @@ -969,22 +969,22 @@ public async Task CreateItemStream_WithEnableBinaryResponseOptions_ShouldSkipStr { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - if (container != null) - { - await container.DeleteContainerStreamAsync(); + if (container != null) + { + await container.DeleteContainerStreamAsync(); } if (database != null) { await database.DeleteAsync(); } - } - } - + } + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task UpsertItemStreamTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task UpsertItemStreamTest(bool binaryEncodingEnabledInClient) { try { @@ -1029,13 +1029,13 @@ public async Task UpsertItemStreamTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task UpsertItemTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task UpsertItemTest(bool binaryEncodingEnabledInClient) { try { @@ -1068,13 +1068,13 @@ public async Task UpsertItemTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task ReplaceItemStreamTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task ReplaceItemStreamTest(bool binaryEncodingEnabledInClient) { try { @@ -1128,63 +1128,63 @@ public async Task ReplaceItemStreamTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - - [DataRow(false)] - [DataRow(true)] - [DataTestMethod] - public async Task ItemStreamIterator(bool useStatelessIterator) - { - IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 3, randomPartitionKey: true); - HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); - - string lastContinuationToken = null; - QueryRequestOptions requestOptions = new QueryRequestOptions() - { - MaxItemCount = 1 - }; - - FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( - continuationToken: lastContinuationToken, - requestOptions: requestOptions); - - while (feedIterator.HasMoreResults) - { - if (useStatelessIterator) - { - feedIterator = this.Container.GetItemQueryStreamIterator( - continuationToken: lastContinuationToken, - requestOptions: requestOptions); - } - - using (ResponseMessage responseMessage = - await feedIterator.ReadNextAsync(this.cancellationToken)) - { - lastContinuationToken = responseMessage.Headers.ContinuationToken; - Assert.AreEqual(responseMessage.ContinuationToken, responseMessage.Headers.ContinuationToken); - Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; - foreach (ToDoActivity toDoActivity in response) - { - if (itemIds.Contains(toDoActivity.id)) - { - itemIds.Remove(toDoActivity.id); - } - } - - Assert.IsNull(responseMessage.Diagnostics.GetQueryMetrics()); - } - - } - - Assert.IsNull(lastContinuationToken); - Assert.AreEqual(itemIds.Count, 0); - } - + } + } + + [DataRow(false)] + [DataRow(true)] + [DataTestMethod] + public async Task ItemStreamIterator(bool useStatelessIterator) + { + IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 3, randomPartitionKey: true); + HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); + + string lastContinuationToken = null; + QueryRequestOptions requestOptions = new QueryRequestOptions() + { + MaxItemCount = 1 + }; + + FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( + continuationToken: lastContinuationToken, + requestOptions: requestOptions); + + while (feedIterator.HasMoreResults) + { + if (useStatelessIterator) + { + feedIterator = this.Container.GetItemQueryStreamIterator( + continuationToken: lastContinuationToken, + requestOptions: requestOptions); + } + + using (ResponseMessage responseMessage = + await feedIterator.ReadNextAsync(this.cancellationToken)) + { + lastContinuationToken = responseMessage.Headers.ContinuationToken; + Assert.AreEqual(responseMessage.ContinuationToken, responseMessage.Headers.ContinuationToken); + Collection response = TestCommon.SerializerCore.FromStream>(responseMessage.Content).Data; + foreach (ToDoActivity toDoActivity in response) + { + if (itemIds.Contains(toDoActivity.id)) + { + itemIds.Remove(toDoActivity.id); + } + } + + Assert.IsNull(responseMessage.Diagnostics.GetQueryMetrics()); + } + + } + + Assert.IsNull(lastContinuationToken); + Assert.AreEqual(itemIds.Count, 0); + } + [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task PartitionKeyDeleteTest(bool binaryEncodingEnabledInClient) + public async Task PartitionKeyDeleteTest(bool binaryEncodingEnabledInClient) { try { @@ -1245,13 +1245,13 @@ public async Task PartitionKeyDeleteTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task PartitionKeyDeleteTestForSubpartitionedContainer(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task PartitionKeyDeleteTestForSubpartitionedContainer(bool binaryEncodingEnabledInClient) { try { @@ -1336,2276 +1336,2275 @@ public async Task PartitionKeyDeleteTestForSubpartitionedContainer(bool binaryEn finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + + [TestMethod] + public async Task ItemCustomSerializerTest() + { + DateTime createDateTime = DateTime.UtcNow; + Dictionary keyValuePairs = new Dictionary() + { + {"test1", 42 }, + {"test42", 9001 } + }; + + dynamic testItem1 = new + { + id = "ItemCustomSerialzierTest1", + cost = (double?)null, + totalCost = 98.2789, + pk = "MyCustomStatus", + taskNum = 4909, + createdDateTime = createDateTime, + statusCode = HttpStatusCode.Accepted, + itemIds = new int[] { 1, 5, 10 }, + dictionary = keyValuePairs + }; + + dynamic testItem2 = new + { + id = "ItemCustomSerialzierTest2", + cost = (double?)null, + totalCost = 98.2789, + pk = "MyCustomStatus", + taskNum = 4909, + createdDateTime = createDateTime, + statusCode = HttpStatusCode.Accepted, + itemIds = new int[] { 1, 5, 10 }, + dictionary = keyValuePairs + }; + + JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() + { + Converters = new List() { new CosmosSerializerHelper.FormatNumbersAsTextConverter() } + }; + + List queryDefinitions = new List() + { + new QueryDefinition("select * from t where t.pk = @pk" ).WithParameter("@pk", testItem1.pk), + new QueryDefinition("select * from t where t.cost = @cost" ).WithParameter("@cost", testItem1.cost), + new QueryDefinition("select * from t where t.taskNum = @taskNum" ).WithParameter("@taskNum", testItem1.taskNum), + new QueryDefinition("select * from t where t.totalCost = @totalCost" ).WithParameter("@totalCost", testItem1.totalCost), + new QueryDefinition("select * from t where t.createdDateTime = @createdDateTime" ).WithParameter("@createdDateTime", testItem1.createdDateTime), + new QueryDefinition("select * from t where t.statusCode = @statusCode" ).WithParameter("@statusCode", testItem1.statusCode), + new QueryDefinition("select * from t where t.itemIds = @itemIds" ).WithParameter("@itemIds", testItem1.itemIds), + new QueryDefinition("select * from t where t.dictionary = @dictionary" ).WithParameter("@dictionary", testItem1.dictionary), + new QueryDefinition("select * from t where t.pk = @pk and t.cost = @cost" ) + .WithParameter("@pk", testItem1.pk) + .WithParameter("@cost", testItem1.cost), + }; + + int toStreamCount = 0; + int fromStreamCount = 0; + CosmosSerializerHelper cosmosSerializerHelper = new CosmosSerializerHelper( + jsonSerializerSettings, + toStreamCallBack: (itemValue) => + { + Type itemType = itemValue?.GetType(); + if (itemValue == null + || itemType == typeof(int) + || itemType == typeof(double) + || itemType == typeof(string) + || itemType == typeof(DateTime) + || itemType == typeof(HttpStatusCode) + || itemType == typeof(int[]) + || itemType == typeof(Dictionary)) + { + toStreamCount++; + } + }, + fromStreamCallback: (item) => fromStreamCount++); + + CosmosClientOptions options = new CosmosClientOptions() + { + Serializer = cosmosSerializerHelper + }; + + CosmosClient clientSerializer = TestCommon.CreateCosmosClient(options); + Container containerSerializer = clientSerializer.GetContainer(this.database.Id, this.Container.Id); + + try + { + await containerSerializer.CreateItemAsync(testItem1); + await containerSerializer.CreateItemAsync(testItem2); + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.Conflict) + { + // Ignore conflicts since the object already exists + } + + foreach (QueryDefinition queryDefinition in queryDefinitions) + { + toStreamCount = 0; + fromStreamCount = 0; + + List allItems = new List(); + int pageCount = 0; + using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( + queryDefinition: queryDefinition)) + { + while (feedIterator.HasMoreResults) + { + // Only need once to verify correct serialization of the query definition + FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); + Assert.AreEqual(response.Count, response.Count()); + allItems.AddRange(response); + pageCount++; + } + } + + Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + foreach (dynamic item in allItems) + { + Assert.IsFalse(string.Equals(testItem1.id, item.id) || string.Equals(testItem2.id, item.id)); + Assert.IsTrue(((JObject)item)["totalCost"].Type == JTokenType.String); + Assert.IsTrue(((JObject)item)["taskNum"].Type == JTokenType.String); + } + + // Each parameter in query spec should be a call to the custom serializer + int parameterCount = queryDefinition.ToSqlQuerySpec().Parameters.Count; + Assert.AreEqual((parameterCount * pageCount) + parameterCount, toStreamCount, $"missing to stream call. Expected: {(parameterCount * pageCount) + parameterCount}, Actual: {toStreamCount} for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + Assert.AreEqual(pageCount, fromStreamCount); + } + } + + [TestMethod] + public async Task QueryStreamValueTest() + { + DateTime createDateTime = DateTime.UtcNow; + + dynamic testItem1 = new + { + id = "testItem1", + cost = (double?)null, + totalCost = 98.2789, + pk = "MyCustomStatus", + taskNum = 4909, + createdDateTime = createDateTime, + statusCode = HttpStatusCode.Accepted, + itemIds = new int[] { 1, 5, 10 }, + itemcode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, + }; + + dynamic testItem2 = new + { + id = "testItem2", + cost = (double?)null, + totalCost = 98.2789, + pk = "MyCustomStatus", + taskNum = 4909, + createdDateTime = createDateTime, + statusCode = HttpStatusCode.Accepted, + itemIds = new int[] { 1, 5, 10 }, + itemcode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, + }; + + //with Custom Serializer. + JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() + { + Converters = new List() { new CosmosSerializerHelper.FormatNumbersAsTextConverter() } + }; + + int toStreamCount = 0; + int fromStreamCount = 0; + CosmosSerializerHelper cosmosSerializerHelper = new CosmosSerializerHelper( + jsonSerializerSettings, + toStreamCallBack: (itemValue) => + { + Type itemType = itemValue?.GetType(); + if (itemValue == null + || itemType == typeof(int) + || itemType == typeof(double) + || itemType == typeof(string) + || itemType == typeof(DateTime) + || itemType == typeof(HttpStatusCode) + || itemType == typeof(int[]) + || itemType == typeof(byte)) + { + toStreamCount++; + } + }, + fromStreamCallback: (item) => fromStreamCount++); + + CosmosClientOptions options = new CosmosClientOptions() + { + Serializer = cosmosSerializerHelper + }; + + CosmosClient clientSerializer = TestCommon.CreateCosmosClient(options); + Container containerSerializer = clientSerializer.GetContainer(this.database.Id, this.Container.Id); + + List queryDefinitions = new List() + { + new QueryDefinition("select * from t where t.pk = @pk" ) + .WithParameterStream("@pk", cosmosSerializerHelper.ToStream(testItem1.pk)), + new QueryDefinition("select * from t where t.cost = @cost" ) + .WithParameterStream("@cost", cosmosSerializerHelper.ToStream(testItem1.cost)), + new QueryDefinition("select * from t where t.taskNum = @taskNum" ) + .WithParameterStream("@taskNum", cosmosSerializerHelper.ToStream(testItem1.taskNum)), + new QueryDefinition("select * from t where t.totalCost = @totalCost" ) + .WithParameterStream("@totalCost", cosmosSerializerHelper.ToStream(testItem1.totalCost)), + new QueryDefinition("select * from t where t.createdDateTime = @createdDateTime" ) + .WithParameterStream("@createdDateTime", cosmosSerializerHelper.ToStream(testItem1.createdDateTime)), + new QueryDefinition("select * from t where t.statusCode = @statusCode" ) + .WithParameterStream("@statusCode", cosmosSerializerHelper.ToStream(testItem1.statusCode)), + new QueryDefinition("select * from t where t.itemIds = @itemIds" ) + .WithParameterStream("@itemIds", cosmosSerializerHelper.ToStream(testItem1.itemIds)), + new QueryDefinition("select * from t where t.itemcode = @itemcode" ) + .WithParameterStream("@itemcode", cosmosSerializerHelper.ToStream(testItem1.itemcode)), + new QueryDefinition("select * from t where t.pk = @pk and t.cost = @cost" ) + .WithParameterStream("@pk", cosmosSerializerHelper.ToStream(testItem1.pk)) + .WithParameterStream("@cost", cosmosSerializerHelper.ToStream(testItem1.cost)), + }; + + try + { + await containerSerializer.CreateItemAsync(testItem1); + await containerSerializer.CreateItemAsync(testItem2); + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.Conflict) + { + // Ignore conflicts since the object already exists + } + + foreach (QueryDefinition queryDefinition in queryDefinitions) + { + toStreamCount = 0; + fromStreamCount = 0; + + List allItems = new List(); + int pageCount = 0; + using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( + queryDefinition: queryDefinition)) + { + while (feedIterator.HasMoreResults) + { + // Only need once to verify correct serialization of the query definition + FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); + string diagnosticString = response.Diagnostics.ToString(); + Assert.IsTrue(diagnosticString.Contains("Query Response Serialization")); + Assert.AreEqual(response.Count, response.Count()); + allItems.AddRange(response); + pageCount++; + } + } + + Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + + // There should be no call to custom serializer since the parameter values are already serialized. + Assert.AreEqual(0, toStreamCount, $"missing to stream call. Expected: 0 , Actual: {toStreamCount} for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + Assert.AreEqual(pageCount, fromStreamCount); + } + + // get result across pages,multiple requests by setting MaxItemCount to 1. + foreach (QueryDefinition queryDefinition in queryDefinitions) + { + toStreamCount = 0; + fromStreamCount = 0; + + List allItems = new List(); + int pageCount = 0; + using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( + queryDefinition: queryDefinition, + requestOptions: new QueryRequestOptions { MaxItemCount = 1 })) + { + while (feedIterator.HasMoreResults) + { + // Only need once to verify correct serialization of the query definition + FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); + Assert.AreEqual(response.Count, response.Count()); + allItems.AddRange(response); + pageCount++; + } + } + + Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + + // There should be no call to custom serializer since the parameter values are already serialized. + Assert.AreEqual(0, toStreamCount, $"missing to stream call. Expected: 0 , Actual: {toStreamCount} for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + Assert.AreEqual(pageCount, fromStreamCount); + } + + + // Standard Cosmos Serializer Used + + CosmosClient clientStandardSerializer = TestCommon.CreateCosmosClient(useCustomSeralizer: false); + Container containerStandardSerializer = clientStandardSerializer.GetContainer(this.database.Id, this.Container.Id); + + testItem1 = ToDoActivity.CreateRandomToDoActivity(); + testItem1.pk = "myPk"; + await containerStandardSerializer.CreateItemAsync(testItem1, new Cosmos.PartitionKey(testItem1.pk)); + + testItem2 = ToDoActivity.CreateRandomToDoActivity(); + testItem2.pk = "myPk"; + await containerStandardSerializer.CreateItemAsync(testItem2, new Cosmos.PartitionKey(testItem2.pk)); + CosmosSerializer cosmosSerializer = containerStandardSerializer.Database.Client.ClientOptions.Serializer; + + queryDefinitions = new List() + { + new QueryDefinition("select * from t where t.pk = @pk" ) + .WithParameterStream("@pk", cosmosSerializer.ToStream(testItem1.pk)), + new QueryDefinition("select * from t where t.cost = @cost" ) + .WithParameterStream("@cost", cosmosSerializer.ToStream(testItem1.cost)), + new QueryDefinition("select * from t where t.taskNum = @taskNum" ) + .WithParameterStream("@taskNum", cosmosSerializer.ToStream(testItem1.taskNum)), + new QueryDefinition("select * from t where t.CamelCase = @CamelCase" ) + .WithParameterStream("@CamelCase", cosmosSerializer.ToStream(testItem1.CamelCase)), + new QueryDefinition("select * from t where t.valid = @valid" ) + .WithParameterStream("@valid", cosmosSerializer.ToStream(testItem1.valid)), + new QueryDefinition("select * from t where t.description = @description" ) + .WithParameterStream("@description", cosmosSerializer.ToStream(testItem1.description)), + new QueryDefinition("select * from t where t.pk = @pk and t.cost = @cost" ) + .WithParameterStream("@pk", cosmosSerializer.ToStream(testItem1.pk)) + .WithParameterStream("@cost", cosmosSerializer.ToStream(testItem1.cost)), + }; + + foreach (QueryDefinition queryDefinition in queryDefinitions) + { + List allItems = new List(); + int pageCount = 0; + using (FeedIterator feedIterator = containerStandardSerializer.GetItemQueryIterator( + queryDefinition: queryDefinition)) + { + while (feedIterator.HasMoreResults) + { + // Only need once to verify correct serialization of the query definition + FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); + Assert.AreEqual(response.Count, response.Count()); + allItems.AddRange(response); + pageCount++; + } + } + + Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); + if (queryDefinition.QueryText.Contains("pk")) + { + Assert.AreEqual(1, pageCount); + } + else + { + Assert.AreEqual(3, pageCount); + } + + + + IReadOnlyList<(string Name, object Value)> parameters1 = queryDefinition.GetQueryParameters(); + IReadOnlyList<(string Name, object Value)> parameters2 = queryDefinition.GetQueryParameters(); + + Assert.AreSame(parameters1, parameters2); + } + } + + [TestMethod] + public async Task ItemIterator() + { + IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 3, randomPartitionKey: true); + HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); + FeedIterator feedIterator = + this.Container.GetItemQueryIterator(); + while (feedIterator.HasMoreResults) + { + foreach (ToDoActivity toDoActivity in await feedIterator.ReadNextAsync(this.cancellationToken)) + { + if (itemIds.Contains(toDoActivity.id)) + { + itemIds.Remove(toDoActivity.id); + } + } + } + + Assert.AreEqual(itemIds.Count, 0); + } + + [TestMethod] + public async Task PerfItemIterator() + { + IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 2000, randomPartitionKey: true); + HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); + + FeedIterator feedIterator = + this.Container.GetItemQueryIterator(); + while (feedIterator.HasMoreResults) + { + foreach (ToDoActivity toDoActivity in await feedIterator.ReadNextAsync(this.cancellationToken)) + { + if (itemIds.Contains(toDoActivity.id)) + { + itemIds.Remove(toDoActivity.id); + } + } + } + + Assert.AreEqual(itemIds.Count, 0); + } + + + [DataRow(1, 1)] + [DataRow(5, 5)] + [DataRow(6, 2)] + [DataTestMethod] + public async Task QuerySinglePartitionItemStreamTest(int perPKItemCount, int maxItemCount) + { + IList deleteList = deleteList = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 3, perPKItemCount: perPKItemCount, randomPartitionKey: true); + ToDoActivity find = deleteList.First(); + + QueryDefinition sql = new QueryDefinition("select * from r where r.pk = @pk").WithParameter("@pk", find.pk); + + int iterationCount = 0; + int totalReadItem = 0; + int expectedIterationCount = perPKItemCount / maxItemCount; + string lastContinuationToken = null; + + do + { + iterationCount++; + FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( + sql, + continuationToken: lastContinuationToken, + requestOptions: new QueryRequestOptions() + { + MaxItemCount = maxItemCount, + MaxConcurrency = 1, + PartitionKey = new Cosmos.PartitionKey(find.pk), + }); + + ResponseMessage response = await feedIterator.ReadNextAsync(); + lastContinuationToken = response.Headers.ContinuationToken; + Assert.AreEqual(response.ContinuationToken, response.Headers.ContinuationToken); + + System.Diagnostics.Trace.TraceInformation($"ContinuationToken: {lastContinuationToken}"); + Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); + + ServerSideCumulativeMetrics metrics = response.Diagnostics.GetQueryMetrics(); + Assert.IsTrue(metrics.PartitionedMetrics.Count > 0); + Assert.IsTrue(metrics.PartitionedMetrics[0].RequestCharge > 0); + Assert.IsTrue(metrics.CumulativeMetrics.TotalTime > TimeSpan.Zero); + Assert.IsTrue(metrics.CumulativeMetrics.QueryPreparationTime > TimeSpan.Zero); + Assert.IsTrue(metrics.TotalRequestCharge > 0); + + if (metrics.CumulativeMetrics.RetrievedDocumentCount >= 1) + { + Assert.IsTrue(metrics.CumulativeMetrics.RetrievedDocumentSize > 0); + Assert.IsTrue(metrics.CumulativeMetrics.DocumentLoadTime > TimeSpan.Zero); + Assert.IsTrue(metrics.CumulativeMetrics.RuntimeExecutionTime > TimeSpan.Zero); + } + else + { + Assert.AreEqual(0, metrics.CumulativeMetrics.RetrievedDocumentSize); + } + + using (StreamReader sr = new StreamReader(response.Content)) + using (JsonTextReader jtr = new JsonTextReader(sr)) + { + ToDoActivity[] results = serializer.Deserialize>(jtr).Data.ToArray(); + ToDoActivity[] readTodoActivities = results.OrderBy(e => e.id) + .ToArray(); + + ToDoActivity[] expectedTodoActivities = deleteList + .Where(e => e.pk == find.pk) + .Where(e => readTodoActivities.Any(e1 => e1.id == e.id)) + .OrderBy(e => e.id) + .ToArray(); + + totalReadItem += expectedTodoActivities.Length; + string expectedSerialized = JsonConvert.SerializeObject(expectedTodoActivities); + string readSerialized = JsonConvert.SerializeObject(readTodoActivities); + System.Diagnostics.Trace.TraceInformation($"Expected: {Environment.NewLine} {expectedSerialized}"); + System.Diagnostics.Trace.TraceInformation($"Read: {Environment.NewLine} {readSerialized}"); + + int count = results.Length; + Assert.AreEqual(maxItemCount, count); + + Assert.AreEqual(expectedSerialized, readSerialized); + + Assert.AreEqual(maxItemCount, expectedTodoActivities.Length); + } + } + while (lastContinuationToken != null); + + Assert.AreEqual(expectedIterationCount, iterationCount); + Assert.AreEqual(perPKItemCount, totalReadItem); + } + + /// + /// Validate multiple partition query + /// + [TestMethod] + public async Task ItemMultiplePartitionQuery() + { + IList itemList = await ToDoActivity.CreateRandomItems(this.Container, 3, randomPartitionKey: true); + + ToDoActivity find = itemList.First(); + QueryDefinition sql = new QueryDefinition("select * from toDoActivity t where t.id = '" + find.id + "'"); + + QueryRequestOptions requestOptions = new QueryRequestOptions() + { + MaxItemCount = 1, + MaxConcurrency = -1, + }; + + FeedIterator feedIterator = this.Container.GetItemQueryIterator( + sql, + requestOptions: requestOptions); + + bool found = false; + while (feedIterator.HasMoreResults) + { + FeedResponse iter = await feedIterator.ReadNextAsync(); + Assert.IsTrue(iter.Count() <= 1); + if (iter.Count() == 1) + { + found = true; + ToDoActivity response = iter.First(); + Assert.AreEqual(find.id, response.id); + } + + ServerSideCumulativeMetrics metrics = iter.Diagnostics.GetQueryMetrics(); + + if (metrics != null) + { + // This assumes that we are using parallel prefetch to hit multiple partitions concurrently + Assert.IsTrue(metrics.PartitionedMetrics.Count == 3); + Assert.IsTrue(metrics.CumulativeMetrics.TotalTime > TimeSpan.Zero); + Assert.IsTrue(metrics.CumulativeMetrics.QueryPreparationTime > TimeSpan.Zero); + Assert.IsTrue(metrics.TotalRequestCharge > 0); + + foreach (ServerSidePartitionedMetrics partitionedMetrics in metrics.PartitionedMetrics) + { + Assert.IsNotNull(partitionedMetrics); + Assert.IsNotNull(partitionedMetrics.FeedRange); + Assert.IsNotNull(partitionedMetrics.PartitionKeyRangeId); + Assert.IsTrue(partitionedMetrics.RequestCharge > 0); + } + + if (metrics.CumulativeMetrics.RetrievedDocumentCount >= 1) + { + Assert.IsTrue(metrics.CumulativeMetrics.RetrievedDocumentSize > 0); + Assert.IsTrue(metrics.CumulativeMetrics.DocumentLoadTime > TimeSpan.Zero); + Assert.IsTrue(metrics.CumulativeMetrics.RuntimeExecutionTime > TimeSpan.Zero); + } + else + { + Assert.AreEqual(0, metrics.CumulativeMetrics.RetrievedDocumentSize); + } + } + else + { + string diag = iter.Diagnostics.ToString(); + Assert.IsNotNull(diag); + } + } + + Assert.IsTrue(found); + } + + /// + /// Validate single partition query using gateway mode. + /// + [TestMethod] + public async Task ItemSinglePartitionQueryGateway() + { + ContainerResponse containerResponse = await this.database.CreateContainerAsync( + new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk")); + + Container createdContainer = (ContainerInlineCore)containerResponse; + CosmosClient client1 = TestCommon.CreateCosmosClient(useGateway: true); + + Container container = client1.GetContainer(this.database.Id, createdContainer.Id); + + string findId = "id2002"; + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity("pk2002", findId); + await container.CreateItemAsync(item); + + QueryDefinition sql = new QueryDefinition("select * from toDoActivity t where t.id = '" + findId + "'"); + + QueryRequestOptions requestOptions = new QueryRequestOptions() + { + MaxBufferedItemCount = 10, + ResponseContinuationTokenLimitInKb = 500, + MaxItemCount = 1, + MaxConcurrency = 1, + }; + + FeedIterator feedIterator = container.GetItemQueryIterator( + sql, + requestOptions: requestOptions); + + bool found = false; + while (feedIterator.HasMoreResults) + { + FeedResponse iter = await feedIterator.ReadNextAsync(); + Assert.IsTrue(iter.Count() <= 1); + if (iter.Count() == 1) + { + found = true; + ToDoActivity response = iter.First(); + Assert.AreEqual(findId, response.id); + } + + ServerSideCumulativeMetrics metrics = iter.Diagnostics.GetQueryMetrics(); + + if (metrics != null) + { + Assert.IsTrue(metrics.PartitionedMetrics.Count == 1); + Assert.IsTrue(metrics.CumulativeMetrics.TotalTime > TimeSpan.Zero); + Assert.IsTrue(metrics.CumulativeMetrics.QueryPreparationTime > TimeSpan.Zero); + Assert.IsTrue(metrics.TotalRequestCharge > 0); + + foreach (ServerSidePartitionedMetrics partitionedMetrics in metrics.PartitionedMetrics) + { + Assert.IsNotNull(partitionedMetrics); + Assert.IsNotNull(partitionedMetrics.FeedRange); + Assert.IsNull(partitionedMetrics.PartitionKeyRangeId); + Assert.IsTrue(partitionedMetrics.RequestCharge > 0); + } + + if (metrics.CumulativeMetrics.RetrievedDocumentCount >= 1) + { + Assert.IsTrue(metrics.CumulativeMetrics.RetrievedDocumentSize > 0); + Assert.IsTrue(metrics.CumulativeMetrics.DocumentLoadTime > TimeSpan.Zero); + Assert.IsTrue(metrics.CumulativeMetrics.RuntimeExecutionTime > TimeSpan.Zero); + } + else + { + Assert.AreEqual(0, metrics.CumulativeMetrics.RetrievedDocumentSize); + } + } + } + + Assert.IsTrue(found); + } + + /// + /// Validate multiple partition query + /// + [TestMethod] + public async Task ItemMultiplePartitionOrderByQueryStream() + { + CultureInfo defaultCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture; + + CultureInfo[] cultureInfoList = new CultureInfo[] + { + defaultCultureInfo, + System.Globalization.CultureInfo.GetCultureInfo("fr-FR") + }; + + IList deleteList = await ToDoActivity.CreateRandomItems( + this.Container, + 300, + randomPartitionKey: true, + randomTaskNumber: true); + + try + { + foreach (CultureInfo cultureInfo in cultureInfoList) + { + System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; + + QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t ORDER BY t.taskNum "); + + QueryRequestOptions requestOptions = new QueryRequestOptions() + { + MaxBufferedItemCount = 10, + ResponseContinuationTokenLimitInKb = 500, + MaxConcurrency = 5, + MaxItemCount = 1, + }; + + List resultList = new List(); + double totalRequstCharge = 0; + FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( + sql, + requestOptions: requestOptions); + + while (feedIterator.HasMoreResults) + { + ResponseMessage iter = await feedIterator.ReadNextAsync(); + Assert.IsTrue(iter.IsSuccessStatusCode); + Assert.IsNull(iter.ErrorMessage); + totalRequstCharge += iter.Headers.RequestCharge; + + ToDoActivity[] activities = TestCommon.SerializerCore.FromStream>(iter.Content).Data.ToArray(); + Assert.AreEqual(1, activities.Length); + ToDoActivity response = activities.First(); + resultList.Add(response); + } + + Assert.AreEqual(deleteList.Count, resultList.Count); + Assert.IsTrue(totalRequstCharge > 0); + + List verifiedOrderBy = deleteList.OrderBy(x => x.taskNum).ToList(); + for (int i = 0; i < verifiedOrderBy.Count(); i++) + { + Assert.AreEqual(verifiedOrderBy[i].taskNum, resultList[i].taskNum); + Assert.AreEqual(verifiedOrderBy[i].id, resultList[i].id); + } + } + } + finally + { + System.Threading.Thread.CurrentThread.CurrentCulture = defaultCultureInfo; + } + } + + /// + /// Validate multiple partition query + /// + [TestMethod] + public async Task ItemMultiplePartitionQueryStream() + { + IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 101, randomPartitionKey: true); + QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t"); + + QueryRequestOptions requestOptions = new QueryRequestOptions() + { + MaxConcurrency = 5, + MaxItemCount = 5, + }; + + List resultList = new List(); + double totalRequstCharge = 0; + FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator(sql, requestOptions: requestOptions); + while (feedIterator.HasMoreResults) + { + ResponseMessage iter = await feedIterator.ReadNextAsync(); + Assert.IsTrue(iter.IsSuccessStatusCode); + Assert.IsNull(iter.ErrorMessage); + totalRequstCharge += iter.Headers.RequestCharge; + ToDoActivity[] response = TestCommon.SerializerCore.FromStream>(iter.Content).Data.ToArray(); + Assert.IsTrue(response.Length <= 5); + resultList.AddRange(response); + } + + Assert.AreEqual(deleteList.Count, resultList.Count); + Assert.IsTrue(totalRequstCharge > 0); + + List verifiedOrderBy = deleteList.OrderBy(x => x.id).ToList(); + resultList = resultList.OrderBy(x => x.id).ToList(); + for (int i = 0; i < verifiedOrderBy.Count(); i++) + { + Assert.AreEqual(verifiedOrderBy[i].taskNum, resultList[i].taskNum); + Assert.AreEqual(verifiedOrderBy[i].id, resultList[i].id); + } + } + + /// + /// Validate multiple partition query + /// + [TestMethod] + public async Task ItemSinglePartitionQueryStream() + { + //Create a 101 random items with random guid PK values + IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 101, perPKItemCount: 1, randomPartitionKey: true); + + // Create 10 items with same pk value + IList findItems = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 1, perPKItemCount: 10, randomPartitionKey: false); + + string findPkValue = findItems.First().pk; + QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t where t.pk = @pkValue").WithParameter("@pkValue", findPkValue); + + + double totalRequstCharge = 0; + FeedIterator setIterator = this.Container.GetItemQueryStreamIterator( + sql, + requestOptions: new QueryRequestOptions() + { + MaxConcurrency = 1, + PartitionKey = new Cosmos.PartitionKey(findPkValue), + }); + + List foundItems = new List(); + while (setIterator.HasMoreResults) + { + ResponseMessage iter = await setIterator.ReadNextAsync(); + Assert.IsTrue(iter.IsSuccessStatusCode); + Assert.IsNull(iter.ErrorMessage); + totalRequstCharge += iter.Headers.RequestCharge; + Collection response = TestCommon.SerializerCore.FromStream>(iter.Content).Data; + foundItems.AddRange(response); + } + + Assert.AreEqual(findItems.Count, foundItems.Count); + Assert.IsFalse(foundItems.Any(x => !string.Equals(x.pk, findPkValue)), "All the found items should have the same PK value"); + Assert.IsTrue(totalRequstCharge > 0); + } + + [TestMethod] + public async Task EpkPointReadTest() + { + string pk = Guid.NewGuid().ToString(); + string epk = new PartitionKey(pk) + .InternalKey + .GetEffectivePartitionKeyString(this.containerSettings.PartitionKey); + + Dictionary properties = new Dictionary() + { + { WFConstants.BackendHeaders.EffectivePartitionKeyString, epk }, + }; + + ItemRequestOptions itemRequestOptions = new ItemRequestOptions + { + IsEffectivePartitionKeyRouting = true, + Properties = properties, + }; + + ResponseMessage response = await this.Container.ReadItemStreamAsync( + Guid.NewGuid().ToString(), + Cosmos.PartitionKey.Null, + itemRequestOptions); + + // Ideally it should be NotFound + // BadReqeust bcoz collection is regular and not binary + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + + await this.Container.CreateItemAsync(new { id = Guid.NewGuid().ToString(), pk = "test" }); + epk = new PartitionKey("test") + .InternalKey + .GetEffectivePartitionKeyString(this.containerSettings.PartitionKey); + properties = new Dictionary() + { + { WFConstants.BackendHeaders.EffectivePartitionKeyString, epk }, + }; + + QueryRequestOptions queryRequestOptions = new QueryRequestOptions + { + IsEffectivePartitionKeyRouting = true, + Properties = properties, + }; + + using (FeedIterator resultSet = this.Container.GetItemQueryIterator( + queryText: "SELECT * FROM root", + requestOptions: queryRequestOptions)) + { + FeedResponse feedresponse = await resultSet.ReadNextAsync(); + Assert.IsNotNull(feedresponse.Resource); + Assert.AreEqual(1, feedresponse.Count()); + } + + } + + /// + /// Validate that if the EPK is set in the options that only a single range is selected. + /// + [TestMethod] + public async Task ItemEpkQuerySingleKeyRangeValidation() + { + ContainerInternal container = null; + try + { + // Create a container large enough to have at least 2 partitions + ContainerResponse containerResponse = await this.database.CreateContainerAsync( + id: Guid.NewGuid().ToString(), + partitionKeyPath: "/pk", + throughput: 15000); + container = (ContainerInlineCore)containerResponse; + + // Get all the partition key ranges to verify there is more than one partition + IRoutingMapProvider routingMapProvider = await this.GetClient().DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); + IReadOnlyList ranges = await routingMapProvider.TryGetOverlappingRangesAsync( + containerResponse.Resource.ResourceId, + new Documents.Routing.Range("00", "FF", isMaxInclusive: true, isMinInclusive: true), + NoOpTrace.Singleton, + forceRefresh: false); + + // If this fails the RUs of the container needs to be increased to ensure at least 2 partitions. + Assert.IsTrue(ranges.Count > 1, " RUs of the container needs to be increased to ensure at least 2 partitions."); + + + ContainerQueryProperties containerQueryProperties = new ContainerQueryProperties( + containerResponse.Resource.ResourceId, + effectivePartitionKeyRanges: null, + //new List> { new Documents.Routing.Range("AA", "AA", true, true) }, + containerResponse.Resource.PartitionKey, + vectorEmbeddingPolicy: null, + containerResponse.Resource.GeospatialConfig.GeospatialType); + + // There should only be one range since the EPK option is set. + List partitionKeyRanges = await CosmosQueryExecutionContextFactory.GetTargetPartitionKeyRangesAsync( + queryClient: new CosmosQueryClientCore(container.ClientContext, container), + resourceLink: container.LinkUri, + partitionedQueryExecutionInfo: null, + containerQueryProperties: containerQueryProperties, + properties: new Dictionary() + { + {"x-ms-effective-partition-key-string", "AA" } + }, + feedRangeInternal: null, + trace: NoOpTrace.Singleton); + + Assert.IsTrue(partitionKeyRanges.Count == 1, "Only 1 partition key range should be selected since the EPK option is set."); + + } + finally + { + if (container != null) + { + await container.DeleteContainerStreamAsync(); + } + } + } + + /// + /// Validate multiple partition query + /// + [TestMethod] + public async Task ItemQueryStreamSerializationSetting() + { + IList deleteList = await ToDoActivity.CreateRandomItems( + container: this.Container, + pkCount: 101, + randomTaskNumber: true); + + QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t ORDER BY t.taskNum"); + + CosmosSerializationFormatOptions options = new CosmosSerializationFormatOptions( + ContentSerializationFormat.CosmosBinary.ToString(), + (content) => JsonNavigator.Create(content), + () => JsonWriter.Create(JsonSerializationFormat.Binary)); + + QueryRequestOptions requestOptions = new QueryRequestOptions() + { + CosmosSerializationFormatOptions = options, + MaxConcurrency = 5, + MaxItemCount = 5, + }; + + List resultList = new List(); + double totalRequstCharge = 0; + FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( + sql, + requestOptions: requestOptions); + + while (feedIterator.HasMoreResults) + { + ResponseMessage response = await feedIterator.ReadNextAsync(); + Assert.IsTrue(response.IsSuccessStatusCode); + Assert.IsNull(response.ErrorMessage); + totalRequstCharge += response.Headers.RequestCharge; + + //Copy the stream and check that the first byte is the correct value + MemoryStream memoryStream = new MemoryStream(); + response.Content.CopyTo(memoryStream); + byte[] content = memoryStream.ToArray(); + response.Content.Position = 0; + + // Examine the first buffer byte to determine the serialization format + byte firstByte = content[0]; + Assert.AreEqual(128, firstByte); + Assert.AreEqual(JsonSerializationFormat.Binary, (JsonSerializationFormat)firstByte); + + IJsonReader reader = JsonReader.Create(content); + IJsonWriter textWriter = JsonWriter.Create(JsonSerializationFormat.Text); + reader.WriteAll(textWriter); + string json = Encoding.UTF8.GetString(textWriter.GetResult().ToArray()); + Assert.IsNotNull(json); + ToDoActivity[] responseActivities = JsonConvert.DeserializeObject>(json).Data.ToArray(); + Assert.IsTrue(responseActivities.Length <= 5); + resultList.AddRange(responseActivities); + } + + Assert.AreEqual(deleteList.Count, resultList.Count); + Assert.IsTrue(totalRequstCharge > 0); + + List verifiedOrderBy = deleteList.OrderBy(x => x.taskNum).ToList(); + for (int i = 0; i < verifiedOrderBy.Count(); i++) + { + Assert.AreEqual(verifiedOrderBy[i].taskNum, resultList[i].taskNum); + Assert.AreEqual(verifiedOrderBy[i].id, resultList[i].id); + } + } + + /// + /// Validate that the max item count works correctly. + /// + /// + [TestMethod] + public async Task ValidateMaxItemCountOnItemQuery() + { + IList deleteList = await ToDoActivity.CreateRandomItems(container: this.Container, pkCount: 1, perPKItemCount: 6, randomPartitionKey: false); + + ToDoActivity toDoActivity = deleteList.First(); + QueryDefinition sql = new QueryDefinition( + "select * from toDoActivity t where t.pk = @pk") + .WithParameter("@pk", toDoActivity.pk); + + // Test max size at 1 + FeedIterator feedIterator = this.Container.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() + { + MaxItemCount = 1, + PartitionKey = new Cosmos.PartitionKey(toDoActivity.pk), + }); + + while (feedIterator.HasMoreResults) + { + FeedResponse iter = await feedIterator.ReadNextAsync(); + Assert.AreEqual(1, iter.Count()); + } + + // Test max size at 2 + FeedIterator setIteratorMax2 = this.Container.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() + { + MaxItemCount = 2, + PartitionKey = new Cosmos.PartitionKey(toDoActivity.pk), + }); + + while (setIteratorMax2.HasMoreResults) + { + FeedResponse iter = await setIteratorMax2.ReadNextAsync(); + Assert.AreEqual(2, iter.Count()); + } + } + + /// + /// Validate that the max item count works correctly. + /// + /// + [TestMethod] + public async Task NegativeQueryTest() + { + await ToDoActivity.CreateRandomItems(container: this.Container, pkCount: 10, perPKItemCount: 20, randomPartitionKey: true); + + try + { + using (FeedIterator resultSet = this.Container.GetItemQueryIterator( + queryText: "SELECT r.id FROM root r WHERE r._ts > 0", + requestOptions: new QueryRequestOptions() + { + ResponseContinuationTokenLimitInKb = 0, + MaxItemCount = 10, + MaxConcurrency = 1 + })) + { + await resultSet.ReadNextAsync(); + } + Assert.Fail("Expected query to fail"); + } + catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) + { + Assert.IsTrue(exception.Message.Contains("continuation token limit specified is not large enough"), exception.Message); + } + + try + { + using (FeedIterator resultSet = this.Container.GetItemQueryIterator( + queryText: "SELECT r.id FROM root r WHERE r._ts >!= 0", + requestOptions: new QueryRequestOptions() { MaxConcurrency = 1 })) + { + await resultSet.ReadNextAsync(); + } + Assert.Fail("Expected query to fail"); + } + catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) + { + Assert.IsTrue(exception.Message.Contains("Syntax error, incorrect syntax near"), exception.Message); + } + } + [TestMethod] - public async Task ItemCustomSerializerTest() + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task ItemRequestOptionAccessConditionTest(bool binaryEncodingEnabledInClient) { - DateTime createDateTime = DateTime.UtcNow; - Dictionary keyValuePairs = new Dictionary() - { - {"test1", 42 }, - {"test42", 9001 } - }; - - dynamic testItem1 = new - { - id = "ItemCustomSerialzierTest1", - cost = (double?)null, - totalCost = 98.2789, - pk = "MyCustomStatus", - taskNum = 4909, - createdDateTime = createDateTime, - statusCode = HttpStatusCode.Accepted, - itemIds = new int[] { 1, 5, 10 }, - dictionary = keyValuePairs - }; - - dynamic testItem2 = new - { - id = "ItemCustomSerialzierTest2", - cost = (double?)null, - totalCost = 98.2789, - pk = "MyCustomStatus", - taskNum = 4909, - createdDateTime = createDateTime, - statusCode = HttpStatusCode.Accepted, - itemIds = new int[] { 1, 5, 10 }, - dictionary = keyValuePairs - }; - - JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() - { - Converters = new List() { new CosmosSerializerHelper.FormatNumbersAsTextConverter() } - }; - - List queryDefinitions = new List() + try { - new QueryDefinition("select * from t where t.pk = @pk" ).WithParameter("@pk", testItem1.pk), - new QueryDefinition("select * from t where t.cost = @cost" ).WithParameter("@cost", testItem1.cost), - new QueryDefinition("select * from t where t.taskNum = @taskNum" ).WithParameter("@taskNum", testItem1.taskNum), - new QueryDefinition("select * from t where t.totalCost = @totalCost" ).WithParameter("@totalCost", testItem1.totalCost), - new QueryDefinition("select * from t where t.createdDateTime = @createdDateTime" ).WithParameter("@createdDateTime", testItem1.createdDateTime), - new QueryDefinition("select * from t where t.statusCode = @statusCode" ).WithParameter("@statusCode", testItem1.statusCode), - new QueryDefinition("select * from t where t.itemIds = @itemIds" ).WithParameter("@itemIds", testItem1.itemIds), - new QueryDefinition("select * from t where t.dictionary = @dictionary" ).WithParameter("@dictionary", testItem1.dictionary), - new QueryDefinition("select * from t where t.pk = @pk and t.cost = @cost" ) - .WithParameter("@pk", testItem1.pk) - .WithParameter("@cost", testItem1.cost), - }; - - int toStreamCount = 0; - int fromStreamCount = 0; - CosmosSerializerHelper cosmosSerializerHelper = new CosmosSerializerHelper( - jsonSerializerSettings, - toStreamCallBack: (itemValue) => + if (binaryEncodingEnabledInClient) { - Type itemType = itemValue?.GetType(); - if (itemValue == null - || itemType == typeof(int) - || itemType == typeof(double) - || itemType == typeof(string) - || itemType == typeof(DateTime) - || itemType == typeof(HttpStatusCode) - || itemType == typeof(int[]) - || itemType == typeof(Dictionary)) - { - toStreamCount++; - } - }, - fromStreamCallback: (item) => fromStreamCount++); - - CosmosClientOptions options = new CosmosClientOptions() - { - Serializer = cosmosSerializerHelper - }; - - CosmosClient clientSerializer = TestCommon.CreateCosmosClient(options); - Container containerSerializer = clientSerializer.GetContainer(this.database.Id, this.Container.Id); + Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, "True"); + } - try - { - await containerSerializer.CreateItemAsync(testItem1); - await containerSerializer.CreateItemAsync(testItem2); - } - catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.Conflict) - { - // Ignore conflicts since the object already exists - } + // Create an item + ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); - foreach (QueryDefinition queryDefinition in queryDefinitions) - { - toStreamCount = 0; - fromStreamCount = 0; + ItemRequestOptions itemRequestOptions = new ItemRequestOptions() + { + IfMatchEtag = Guid.NewGuid().ToString(), + }; - List allItems = new List(); - int pageCount = 0; - using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( - queryDefinition: queryDefinition)) + using (ResponseMessage responseMessage = await this.Container.UpsertItemStreamAsync( + streamPayload: TestCommon.SerializerCore.ToStream(testItem), + partitionKey: new Cosmos.PartitionKey(testItem.pk), + requestOptions: itemRequestOptions)) { - while (feedIterator.HasMoreResults) - { - // Only need once to verify correct serialization of the query definition - FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); - Assert.AreEqual(response.Count, response.Count()); - allItems.AddRange(response); - pageCount++; - } + Assert.IsNotNull(responseMessage); + Assert.IsNull(responseMessage.Content); + Assert.AreEqual(HttpStatusCode.PreconditionFailed, responseMessage.StatusCode, responseMessage.ErrorMessage); + Assert.AreNotEqual(responseMessage.Headers.ActivityId, Guid.Empty); + Assert.IsTrue(responseMessage.Headers.RequestCharge > 0); + Assert.IsFalse(string.IsNullOrEmpty(responseMessage.ErrorMessage)); + Assert.IsTrue(responseMessage.ErrorMessage.Contains("One of the specified pre-condition is not met")); } - Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - foreach (dynamic item in allItems) + try { - Assert.IsFalse(string.Equals(testItem1.id, item.id) || string.Equals(testItem2.id, item.id)); - Assert.IsTrue(((JObject)item)["totalCost"].Type == JTokenType.String); - Assert.IsTrue(((JObject)item)["taskNum"].Type == JTokenType.String); + ItemResponse response = await this.Container.UpsertItemAsync( + item: testItem, + requestOptions: itemRequestOptions); + Assert.Fail("Access condition should have failed"); + } + catch (CosmosException e) + { + Assert.IsNotNull(e); + Assert.AreEqual(HttpStatusCode.PreconditionFailed, e.StatusCode, e.Message); + Assert.AreNotEqual(e.ActivityId, Guid.Empty); + Assert.IsTrue(e.RequestCharge > 0); + string expectedResponseBody = $"{Environment.NewLine}Errors : [{Environment.NewLine} \"One of the specified pre-condition is not met. Learn more: https://aka.ms/CosmosDB/sql/errors/precondition-failed\"{Environment.NewLine}]{Environment.NewLine}"; + Assert.AreEqual(expectedResponseBody, e.ResponseBody); + string expectedMessage = $"Response status code does not indicate success: PreconditionFailed (412); Substatus: 0; ActivityId: {e.ActivityId}; Reason: ({expectedResponseBody});"; + Assert.AreEqual(expectedMessage, e.Message); + } + finally + { + ItemResponse deleteResponse = await this.Container.DeleteItemAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id); + Assert.IsNotNull(deleteResponse); } - - // Each parameter in query spec should be a call to the custom serializer - int parameterCount = queryDefinition.ToSqlQuerySpec().Parameters.Count; - Assert.AreEqual((parameterCount * pageCount) + parameterCount, toStreamCount, $"missing to stream call. Expected: {(parameterCount * pageCount) + parameterCount}, Actual: {toStreamCount} for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - Assert.AreEqual(pageCount, fromStreamCount); } - } - + finally + { + Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); + } + } + [TestMethod] - public async Task QueryStreamValueTest() + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + public async Task ItemReplaceAsyncTest(bool binaryEncodingEnabledInClient) { - DateTime createDateTime = DateTime.UtcNow; - - dynamic testItem1 = new - { - id = "testItem1", - cost = (double?)null, - totalCost = 98.2789, - pk = "MyCustomStatus", - taskNum = 4909, - createdDateTime = createDateTime, - statusCode = HttpStatusCode.Accepted, - itemIds = new int[] { 1, 5, 10 }, - itemcode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, - }; - - dynamic testItem2 = new - { - id = "testItem2", - cost = (double?)null, - totalCost = 98.2789, - pk = "MyCustomStatus", - taskNum = 4909, - createdDateTime = createDateTime, - statusCode = HttpStatusCode.Accepted, - itemIds = new int[] { 1, 5, 10 }, - itemcode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, - }; - - //with Custom Serializer. - JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() + try { - Converters = new List() { new CosmosSerializerHelper.FormatNumbersAsTextConverter() } - }; - - int toStreamCount = 0; - int fromStreamCount = 0; - CosmosSerializerHelper cosmosSerializerHelper = new CosmosSerializerHelper( - jsonSerializerSettings, - toStreamCallBack: (itemValue) => + if (binaryEncodingEnabledInClient) { - Type itemType = itemValue?.GetType(); - if (itemValue == null - || itemType == typeof(int) - || itemType == typeof(double) - || itemType == typeof(string) - || itemType == typeof(DateTime) - || itemType == typeof(HttpStatusCode) - || itemType == typeof(int[]) - || itemType == typeof(byte)) - { - toStreamCount++; - } - }, - fromStreamCallback: (item) => fromStreamCount++); + Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, "True"); + } - CosmosClientOptions options = new CosmosClientOptions() - { - Serializer = cosmosSerializerHelper - }; + // Create an item + ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); - CosmosClient clientSerializer = TestCommon.CreateCosmosClient(options); - Container containerSerializer = clientSerializer.GetContainer(this.database.Id, this.Container.Id); + string originalId = testItem.id; + testItem.id = Guid.NewGuid().ToString(); - List queryDefinitions = new List() - { - new QueryDefinition("select * from t where t.pk = @pk" ) - .WithParameterStream("@pk", cosmosSerializerHelper.ToStream(testItem1.pk)), - new QueryDefinition("select * from t where t.cost = @cost" ) - .WithParameterStream("@cost", cosmosSerializerHelper.ToStream(testItem1.cost)), - new QueryDefinition("select * from t where t.taskNum = @taskNum" ) - .WithParameterStream("@taskNum", cosmosSerializerHelper.ToStream(testItem1.taskNum)), - new QueryDefinition("select * from t where t.totalCost = @totalCost" ) - .WithParameterStream("@totalCost", cosmosSerializerHelper.ToStream(testItem1.totalCost)), - new QueryDefinition("select * from t where t.createdDateTime = @createdDateTime" ) - .WithParameterStream("@createdDateTime", cosmosSerializerHelper.ToStream(testItem1.createdDateTime)), - new QueryDefinition("select * from t where t.statusCode = @statusCode" ) - .WithParameterStream("@statusCode", cosmosSerializerHelper.ToStream(testItem1.statusCode)), - new QueryDefinition("select * from t where t.itemIds = @itemIds" ) - .WithParameterStream("@itemIds", cosmosSerializerHelper.ToStream(testItem1.itemIds)), - new QueryDefinition("select * from t where t.itemcode = @itemcode" ) - .WithParameterStream("@itemcode", cosmosSerializerHelper.ToStream(testItem1.itemcode)), - new QueryDefinition("select * from t where t.pk = @pk and t.cost = @cost" ) - .WithParameterStream("@pk", cosmosSerializerHelper.ToStream(testItem1.pk)) - .WithParameterStream("@cost", cosmosSerializerHelper.ToStream(testItem1.cost)), - }; + ItemResponse response = await this.Container.ReplaceItemAsync( + id: originalId, + item: testItem); - try - { - await containerSerializer.CreateItemAsync(testItem1); - await containerSerializer.CreateItemAsync(testItem2); - } - catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.Conflict) - { - // Ignore conflicts since the object already exists - } + Assert.AreEqual(testItem.id, response.Resource.id); + Assert.AreNotEqual(originalId, response.Resource.id); - foreach (QueryDefinition queryDefinition in queryDefinitions) - { - toStreamCount = 0; - fromStreamCount = 0; + string originalStatus = testItem.pk; + testItem.pk = Guid.NewGuid().ToString(); - List allItems = new List(); - int pageCount = 0; - using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( - queryDefinition: queryDefinition)) + try { - while (feedIterator.HasMoreResults) - { - // Only need once to verify correct serialization of the query definition - FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); - string diagnosticString = response.Diagnostics.ToString(); - Assert.IsTrue(diagnosticString.Contains("Query Response Serialization")); - Assert.AreEqual(response.Count, response.Count()); - allItems.AddRange(response); - pageCount++; - } + response = await this.Container.ReplaceItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(originalStatus), + item: testItem); + Assert.Fail("Replace changing partition key is not supported."); + } + catch (CosmosException ce) + { + Assert.AreEqual((HttpStatusCode)400, ce.StatusCode); } - - Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - - // There should be no call to custom serializer since the parameter values are already serialized. - Assert.AreEqual(0, toStreamCount, $"missing to stream call. Expected: 0 , Actual: {toStreamCount} for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - Assert.AreEqual(pageCount, fromStreamCount); } - - // get result across pages,multiple requests by setting MaxItemCount to 1. - foreach (QueryDefinition queryDefinition in queryDefinitions) + finally + { + Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); + } + } + + [TestMethod] + public async Task ItemPatchFailureTest() + { + // Create an item + ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + List patchOperations = new List + { + PatchOperation.Add("/nonExistentParent/Child", "bar"), + PatchOperation.Remove("/cost") + }; + + // item does not exist - 404 Resource Not Found error + try + { + await containerInternal.PatchItemAsync( + id: Guid.NewGuid().ToString(), + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.Fail("Patch operation should fail if the item doesn't exist."); + } + catch (CosmosException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode); + Assert.IsTrue(ex.Message.Contains("Resource Not Found")); + Assert.IsTrue(ex.Message.Contains("https://aka.ms/cosmosdb-tsg-not-found")); + CosmosItemTests.ValidateCosmosException(ex); + } + + // adding a child when parent / ancestor does not exist - 400 BadRequest response + try + { + await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.Fail("Patch operation should fail for malformed PatchSpecification."); + } + catch (CosmosException ex) + { + Assert.AreEqual(HttpStatusCode.BadRequest, ex.StatusCode); + Assert.IsTrue(ex.Message.Contains(@"For Operation(1): Add Operation can only create a child object of an existing node(array or object) and cannot create path recursively, no path found beyond: 'nonExistentParent'. Learn more: https://aka.ms/cosmosdbpatchdocs"), ex.Message); + CosmosItemTests.ValidateCosmosException(ex); + } + + // precondition failure - 412 response + PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() + { + IfMatchEtag = Guid.NewGuid().ToString() + }; + + try + { + await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations, + requestOptions); + + Assert.Fail("Patch operation should fail in case of pre-condition failure."); + } + catch (CosmosException ex) + { + Assert.AreEqual(HttpStatusCode.PreconditionFailed, ex.StatusCode); + Assert.IsTrue(ex.Message.Contains("One of the specified pre-condition is not met")); + CosmosItemTests.ValidateCosmosException(ex); + } + } + + [TestMethod] + public async Task ItemPatchSuccessTest() + { + // Create an item + ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + int originalTaskNum = testItem.taskNum; + int newTaskNum = originalTaskNum + 1; + //Int16 one = 1; + + Assert.IsNull(testItem.children[1].pk); + List patchOperations = new List() + { + PatchOperation.Set("/children/0/description", "testSet"), + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Replace("/taskNum", newTaskNum), + //PatchOperation.Increment("/taskNum", one) + + PatchOperation.Set("/children/1/nullableInt",null) + }; + + // without content response + PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() + { + EnableContentResponseOnWrite = false + }; + + ItemResponse response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations, + requestOptions); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNull(response.Resource); + + // read resource to validate the patch operation + response = await containerInternal.ReadItemAsync( + testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk)); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + Assert.AreEqual("testSet", response.Resource.children[0].description); + Assert.AreEqual("patched", response.Resource.children[1].pk); + Assert.IsNull(response.Resource.description); + Assert.AreEqual(newTaskNum, response.Resource.taskNum); + Assert.IsNull(response.Resource.children[1].nullableInt); + + patchOperations.Clear(); + patchOperations.Add(PatchOperation.Add("/children/0/cost", 1)); + // with content response + response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + Assert.AreEqual(1, response.Resource.children[0].cost); + + patchOperations.Clear(); + patchOperations.Add(PatchOperation.Set("/children/0/id", null)); + // with content response + response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + Assert.AreEqual(null, response.Resource.children[0].id); + + patchOperations.Clear(); + patchOperations.Add(PatchOperation.Add("/children/1/description", "Child#1")); + patchOperations.Add(PatchOperation.Move("/children/0/description", "/description")); + patchOperations.Add(PatchOperation.Move("/children/1/description", "/children/0/description")); + // with content response + response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + Assert.AreEqual("testSet", response.Resource.description); + Assert.AreEqual("Child#1", response.Resource.children[0].description); + Assert.IsNull(response.Resource.children[1].description); + } + + [TestMethod] + public async Task PatchItemStreamTest() + { + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + List patchOperations = new List() + { + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Replace("/taskNum", testItem.taskNum+1) + }; + + PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() + { + FilterPredicate = "from root where root.x = 3" + }; + + // Patch a non-existing item. It should fail, and not throw an exception. + using (ResponseMessage response = await containerInternal.PatchItemStreamAsync( + partitionKey: new Cosmos.PartitionKey(testItem.pk), + id: testItem.id, + patchOperations: patchOperations, + requestOptions: requestOptions)) + { + Assert.IsFalse(response.IsSuccessStatusCode); + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode, response.ErrorMessage); + } + + using (Stream stream = TestCommon.SerializerCore.ToStream(testItem)) + { + // Create the item + using (ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), streamPayload: stream)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + } + } + + // Patch + using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperations)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + // Read and validate + ItemResponse itemResponse = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); + Assert.AreEqual(HttpStatusCode.OK, itemResponse.StatusCode); + Assert.IsNotNull(itemResponse.Resource); + Assert.AreEqual("patched", itemResponse.Resource.children[1].pk); + Assert.IsNull(itemResponse.Resource.description); + Assert.AreEqual(testItem.taskNum + 1, itemResponse.Resource.taskNum); + + // Delete + using (ResponseMessage deleteResponse = await this.Container.DeleteItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id)) + { + Assert.IsNotNull(deleteResponse); + Assert.AreEqual(deleteResponse.StatusCode, HttpStatusCode.NoContent); + } + } + + [TestMethod] + public async Task ContainerRecreateScenarioGatewayTest() + { + ContainerResponse response = await this.database.CreateContainerAsync( + new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk")); + + Container createdContainer = (ContainerInlineCore)response; + + CosmosClient client1 = TestCommon.CreateCosmosClient(useGateway: true); + CosmosClient client2 = TestCommon.CreateCosmosClient(useGateway: true); + + Container container1 = client1.GetContainer(this.database.Id, createdContainer.Id); + Container container2 = client2.GetContainer(this.database.Id, createdContainer.Id); + Cosmos.Database database2 = client2.GetDatabase(this.database.Id); + + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity("pk2002", "id2002"); + await container1.CreateItemAsync(item); + + await container2.DeleteContainerAsync(); + await database2.CreateContainerAsync(createdContainer.Id, "/pk"); + + container2 = database2.GetContainer(this.Container.Id); + await container2.CreateItemAsync(item); + + // should not throw exception + await this.Container.ReadItemAsync("id2002", new Cosmos.PartitionKey("pk2002")); + } + + [TestMethod] + public async Task BatchPatchConditionTest() + { + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + using (Stream stream = TestCommon.SerializerCore.ToStream(testItem)) + { + // Create the item + using (ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), streamPayload: stream)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + } + } + + List patchOperations = new List() + { + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Add("/taskNum", 8) + }; + + using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperations)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + List patchOperationsUpdateTaskNum12 = new List() + { + PatchOperation.Replace("/taskNum", 12) + }; + + TransactionalBatchPatchItemRequestOptions requestOptionsFalse = new TransactionalBatchPatchItemRequestOptions() + { + FilterPredicate = "from c where c.taskNum = 3" + }; + + TransactionalBatchInternal transactionalBatchInternalFalse = (TransactionalBatchInternal)containerInternal.CreateTransactionalBatch(new Cosmos.PartitionKey(testItem.pk)); + transactionalBatchInternalFalse.PatchItem(id: testItem.id, patchOperationsUpdateTaskNum12, requestOptionsFalse); + using (TransactionalBatchResponse batchResponse = await transactionalBatchInternalFalse.ExecuteAsync()) + { + Assert.IsNotNull(batchResponse); + Assert.AreEqual(HttpStatusCode.PreconditionFailed, batchResponse.StatusCode); + } + + { + // Read and validate + ItemResponse itemResponsemid = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); + Assert.AreEqual(HttpStatusCode.OK, itemResponsemid.StatusCode); + Assert.IsNotNull(itemResponsemid.Resource); + Assert.AreEqual("patched", itemResponsemid.Resource.children[1].pk); + Assert.IsNull(itemResponsemid.Resource.description); + Assert.AreEqual(8, itemResponsemid.Resource.taskNum); + } + + List patchOperationsUpdateTaskNum14 = new List() + { + PatchOperation.Increment("/taskNum", 6) + }; + + TransactionalBatchPatchItemRequestOptions requestOptionsTrue = new TransactionalBatchPatchItemRequestOptions() + { + FilterPredicate = "from root where root.taskNum = 8" + }; + + TransactionalBatchInternal transactionalBatchInternalTrue = (TransactionalBatchInternal)containerInternal.CreateTransactionalBatch(new Cosmos.PartitionKey(testItem.pk)); + transactionalBatchInternalTrue.PatchItem(id: testItem.id, patchOperationsUpdateTaskNum14, requestOptionsTrue); + using (TransactionalBatchResponse batchResponse = await transactionalBatchInternalTrue.ExecuteAsync()) + { + Assert.IsNotNull(batchResponse); + Assert.AreEqual(HttpStatusCode.OK, batchResponse.StatusCode); + } + + // Read and validate + ItemResponse itemResponse = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); + Assert.AreEqual(HttpStatusCode.OK, itemResponse.StatusCode); + Assert.IsNotNull(itemResponse.Resource); + Assert.AreEqual("patched", itemResponse.Resource.children[1].pk); + Assert.IsNull(itemResponse.Resource.description); + Assert.AreEqual(14, itemResponse.Resource.taskNum); + + // Delete + using (ResponseMessage deleteResponse = await this.Container.DeleteItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id)) + { + Assert.IsNotNull(deleteResponse); + Assert.AreEqual(deleteResponse.StatusCode, HttpStatusCode.NoContent); + } + } + + [TestMethod] + public async Task PatchConditionTest() + { + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + using (Stream stream = TestCommon.SerializerCore.ToStream(testItem)) + { + // Create the item + using (ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), streamPayload: stream)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + } + } + + List patchOperations = new List() + { + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Add("/taskNum", 8) + }; + + // Patch + using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperations)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + List patchOperationsUpdateTaskNum12 = new List() + { + PatchOperation.Replace("/taskNum", 12) + }; + + PatchItemRequestOptions requestOptionsFalse = new PatchItemRequestOptions() + { + FilterPredicate = "from c where c.taskNum = 3" + }; + + // Patch that fails due to condition not met. + using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperationsUpdateTaskNum12, requestOptions: requestOptionsFalse)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.PreconditionFailed, response.StatusCode); + } + + { + // Read and validate + ItemResponse itemResponsemid = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); + Assert.AreEqual(HttpStatusCode.OK, itemResponsemid.StatusCode); + Assert.IsNotNull(itemResponsemid.Resource); + Assert.AreEqual("patched", itemResponsemid.Resource.children[1].pk); + Assert.IsNull(itemResponsemid.Resource.description); + Assert.AreEqual(8, itemResponsemid.Resource.taskNum); + } + + List patchOperationsUpdateTaskNum14 = new List() + { + PatchOperation.Increment("/taskNum", 6) + }; + + PatchItemRequestOptions requestOptionsTrue = new PatchItemRequestOptions() + { + FilterPredicate = "from root where root.taskNum = 8" + }; + + // Patch + using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperationsUpdateTaskNum14, requestOptions: requestOptionsTrue)) + { + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + // Read and validate + ItemResponse itemResponse = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); + Assert.AreEqual(HttpStatusCode.OK, itemResponse.StatusCode); + Assert.IsNotNull(itemResponse.Resource); + Assert.AreEqual("patched", itemResponse.Resource.children[1].pk); + Assert.IsNull(itemResponse.Resource.description); + Assert.AreEqual(14, itemResponse.Resource.taskNum); + + // Delete + using (ResponseMessage deleteResponse = await this.Container.DeleteItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id)) + { + Assert.IsNotNull(deleteResponse); + Assert.AreEqual(deleteResponse.StatusCode, HttpStatusCode.NoContent); + } + } + + [TestMethod] + public async Task ItemPatchViaGatewayTest() + { + CosmosClient gatewayClient = TestCommon.CreateCosmosClient(useGateway: true); + Container gatewayContainer = gatewayClient.GetContainer(this.database.Id, this.Container.Id); + ContainerInternal containerInternal = (ContainerInternal)gatewayContainer; + + // Create an item + ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(gatewayContainer, 1, randomPartitionKey: true)).First(); + + int originalTaskNum = testItem.taskNum; + int newTaskNum = originalTaskNum + 1; + + Assert.IsNull(testItem.children[1].pk); + + List patchOperations = new List() + { + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Replace("/taskNum", newTaskNum) + }; + + ItemResponse response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + Assert.AreEqual("patched", response.Resource.children[1].pk); + Assert.IsNull(response.Resource.description); + Assert.AreEqual(newTaskNum, response.Resource.taskNum); + } + + [TestMethod] + public async Task ItemPatchCustomSerializerTest() + { + CosmosClientOptions clientOptions = new CosmosClientOptions() + { + Serializer = new CosmosJsonDotNetSerializer( + new JsonSerializerSettings() + { + DateFormatString = "dd / MM / yy hh:mm" + }) + }; + + CosmosClient customSerializationClient = TestCommon.CreateCosmosClient(clientOptions); + Container customSerializationContainer = customSerializationClient.GetContainer(this.database.Id, this.Container.Id); + ContainerInternal containerInternal = (ContainerInternal)customSerializationContainer; + + ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(customSerializationContainer, 1, randomPartitionKey: true)).First(); + + PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() + { + EnableContentResponseOnWrite = false + }; + + DateTime patchDate = new DateTime(2020, 07, 01, 01, 02, 03); + Stream patchDateStreamInput = new CosmosJsonDotNetSerializer().ToStream(patchDate); + string streamDateJson; + using (Stream stream = new MemoryStream()) + { + patchDateStreamInput.CopyTo(stream); + stream.Position = 0; + patchDateStreamInput.Position = 0; + using (StreamReader streamReader = new StreamReader(stream)) + { + streamDateJson = streamReader.ReadToEnd(); + } + } + + List patchOperations = new List() + { + PatchOperation.Add("/date", patchDate), + PatchOperation.Add("/dateStream", patchDateStreamInput) + }; + + ItemResponse response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations, + requestOptions); + + JsonSerializerSettings jsonSettings = new JsonSerializerSettings(); + jsonSettings.DateFormatString = "dd / MM / yy hh:mm"; + string dateJson = JsonConvert.SerializeObject(patchDate, jsonSettings); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + // regular container + response = await this.Container.ReadItemAsync( + testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk)); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + Assert.IsTrue(dateJson.Contains(response.Resource["date"].ToString())); + Assert.AreEqual(patchDate.ToString(), response.Resource["dateStream"].ToString()); + Assert.AreNotEqual(response.Resource["date"], response.Resource["dateStream"]); + } + + [TestMethod] + public async Task ItemPatchStreamInputTest() + { + dynamic testItem = new + { + id = "test", + cost = (double?)null, + totalCost = 98.2789, + pk = "MyCustomStatus", + taskNum = 4909, + itemIds = new int[] { 1, 5, 10 }, + itemCode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, + }; + + // Create item + await this.Container.CreateItemAsync(item: testItem); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + dynamic testItemUpdated = new + { + cost = 100, + totalCost = 198.2789, + taskNum = 4910, + itemCode = new byte?[3] { 0x14, (byte)'\0', (byte)'{' } + }; + + CosmosJsonDotNetSerializer cosmosJsonDotNetSerializer = new CosmosJsonDotNetSerializer(); + + List patchOperations = new List() + { + PatchOperation.Replace("/cost", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.cost)), + PatchOperation.Replace("/totalCost", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.totalCost)), + PatchOperation.Replace("/taskNum", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.taskNum)), + PatchOperation.Replace("/itemCode", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.itemCode)), + }; + + ItemResponse response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + + Assert.AreEqual(testItemUpdated.cost.ToString(), response.Resource.cost.ToString()); + Assert.AreEqual(testItemUpdated.totalCost.ToString(), response.Resource.totalCost.ToString()); + Assert.AreEqual(testItemUpdated.taskNum.ToString(), response.Resource.taskNum.ToString()); + Assert.AreEqual(testItemUpdated.itemCode[0].ToString(), response.Resource.itemCode[0].ToString()); + Assert.AreEqual(testItemUpdated.itemCode[1].ToString(), response.Resource.itemCode[1].ToString()); + Assert.AreEqual(testItemUpdated.itemCode[2].ToString(), response.Resource.itemCode[2].ToString()); + } + + // Read write non partition Container item. + [TestMethod] + public async Task ReadNonPartitionItemAsync() + { + ContainerInternal fixedContainer = null; + try + { + fixedContainer = await NonPartitionedContainerHelper.CreateNonPartitionedContainer( + this.database, + "ReadNonPartition" + Guid.NewGuid()); + + await NonPartitionedContainerHelper.CreateItemInNonPartitionedContainer(fixedContainer, nonPartitionItemId); + await NonPartitionedContainerHelper.CreateUndefinedPartitionItem((ContainerInlineCore)this.Container, undefinedPartitionItemId); + + ContainerResponse containerResponse = await fixedContainer.ReadContainerAsync(); + Assert.IsTrue(containerResponse.Resource.PartitionKey.Paths.Count > 0); + Assert.AreEqual(PartitionKey.SystemKeyPath, containerResponse.Resource.PartitionKey.Paths[0]); + + //Reading item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. + ItemResponse response = await fixedContainer.ReadItemAsync( + partitionKey: Cosmos.PartitionKey.None, + id: nonPartitionItemId); + + Assert.IsNotNull(response.Resource); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(nonPartitionItemId, response.Resource.id); + + //Adding item to fixed container with CosmosContainerSettings.NonePartitionKeyValue. + ToDoActivity itemWithoutPK = ToDoActivity.CreateRandomToDoActivity(); + ItemResponse createResponseWithoutPk = await fixedContainer.CreateItemAsync( + item: itemWithoutPK, + partitionKey: Cosmos.PartitionKey.None); + + Assert.IsNotNull(createResponseWithoutPk.Resource); + Assert.AreEqual(HttpStatusCode.Created, createResponseWithoutPk.StatusCode); + Assert.AreEqual(itemWithoutPK.id, createResponseWithoutPk.Resource.id); + + //Updating item on fixed container with CosmosContainerSettings.NonePartitionKeyValue. + itemWithoutPK.pk = "updatedStatus"; + ItemResponse updateResponseWithoutPk = await fixedContainer.ReplaceItemAsync( + id: itemWithoutPK.id, + item: itemWithoutPK, + partitionKey: Cosmos.PartitionKey.None); + + Assert.IsNotNull(updateResponseWithoutPk.Resource); + Assert.AreEqual(HttpStatusCode.OK, updateResponseWithoutPk.StatusCode); + Assert.AreEqual(itemWithoutPK.id, updateResponseWithoutPk.Resource.id); + + //Adding item to fixed container with non-none PK. + ToDoActivityAfterMigration itemWithPK = this.CreateRandomToDoActivityAfterMigration("TestPk"); + ItemResponse createResponseWithPk = await fixedContainer.CreateItemAsync( + item: itemWithPK); + + Assert.IsNotNull(createResponseWithPk.Resource); + Assert.AreEqual(HttpStatusCode.Created, createResponseWithPk.StatusCode); + Assert.AreEqual(itemWithPK.id, createResponseWithPk.Resource.id); + + //Quering items on fixed container with cross partition enabled. + QueryDefinition sql = new QueryDefinition("select * from r"); + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() { MaxConcurrency = 1, MaxItemCount = 10 })) + { + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(3, queryResponse.Count()); + } + } + + //Reading all items on fixed container. + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator(requestOptions: new QueryRequestOptions() { MaxItemCount = 10 })) + { + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(3, queryResponse.Count()); + } + } + + //Quering items on fixed container with CosmosContainerSettings.NonePartitionKeyValue. + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( + new QueryDefinition("select * from r"), + requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = Cosmos.PartitionKey.None, })) + { + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(2, queryResponse.Count()); + } + } + + //use ReadFeed on fixed container with CosmosContainerSettings.NonePartitionKeyValue. + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( + queryText: null, + requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = Cosmos.PartitionKey.None, })) + { + while (feedIterator.HasMoreResults) + { + FeedResponse readFeedResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(2, readFeedResponse.Count()); + } + } + + //Quering items on fixed container with non-none PK. + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = new Cosmos.PartitionKey(itemWithPK.partitionKey) })) + { + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(1, queryResponse.Count()); + } + } + + //ReadFeed on on fixed container with non-none PK. + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( + queryText: null, + requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = new Cosmos.PartitionKey(itemWithPK.partitionKey) })) + { + while (feedIterator.HasMoreResults) + { + FeedResponse readFeedResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(1, readFeedResponse.Count()); + } + } + + //Deleting item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. + ItemResponse deleteResponseWithoutPk = await fixedContainer.DeleteItemAsync( + partitionKey: Cosmos.PartitionKey.None, + id: itemWithoutPK.id); + + Assert.IsNull(deleteResponseWithoutPk.Resource); + Assert.AreEqual(HttpStatusCode.NoContent, deleteResponseWithoutPk.StatusCode); + + //Deleting item from fixed container with non-none PK. + ItemResponse deleteResponseWithPk = await fixedContainer.DeleteItemAsync( + partitionKey: new Cosmos.PartitionKey(itemWithPK.partitionKey), + id: itemWithPK.id); + + Assert.IsNull(deleteResponseWithPk.Resource); + Assert.AreEqual(HttpStatusCode.NoContent, deleteResponseWithPk.StatusCode); + + //Reading item from partitioned container with CosmosContainerSettings.NonePartitionKeyValue. + ItemResponse undefinedItemResponse = await this.Container.ReadItemAsync( + partitionKey: Cosmos.PartitionKey.None, + id: undefinedPartitionItemId); + + Assert.IsNotNull(undefinedItemResponse.Resource); + Assert.AreEqual(HttpStatusCode.OK, undefinedItemResponse.StatusCode); + Assert.AreEqual(undefinedPartitionItemId, undefinedItemResponse.Resource.id); + } + finally + { + if (fixedContainer != null) + { + await fixedContainer.DeleteContainerStreamAsync(); + } + } + } + + // Move the data from None Partition to other logical partitions + [TestMethod] + public async Task MigrateDataInNonPartitionContainer() + { + ContainerInternal fixedContainer = null; + try + { + fixedContainer = await NonPartitionedContainerHelper.CreateNonPartitionedContainer( + this.database, + "ItemTestMigrateData" + Guid.NewGuid().ToString()); + + const int ItemsToCreate = 4; + // Insert a few items with no Partition Key + for (int i = 0; i < ItemsToCreate; i++) + { + await NonPartitionedContainerHelper.CreateItemInNonPartitionedContainer(fixedContainer, Guid.NewGuid().ToString()); + } + + // Read the container metadata + ContainerResponse containerResponse = await fixedContainer.ReadContainerAsync(); + + // Query items on the container that have no partition key value + int resultsFetched = 0; + QueryDefinition sql = new QueryDefinition("select * from r "); + FeedIterator setIterator = fixedContainer.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() { MaxItemCount = 2, PartitionKey = Cosmos.PartitionKey.None, }); + + while (setIterator.HasMoreResults) + { + FeedResponse queryResponse = await setIterator.ReadNextAsync(); + resultsFetched += queryResponse.Count(); + + // For the items returned with NonePartitionKeyValue + IEnumerator iter = queryResponse.GetEnumerator(); + while (iter.MoveNext()) + { + ToDoActivity activity = iter.Current; + + // Re-Insert into container with a partition key + ToDoActivityAfterMigration itemWithPK = new ToDoActivityAfterMigration + { id = activity.id, cost = activity.cost, description = activity.description, partitionKey = "TestPK", taskNum = activity.taskNum }; + ItemResponse createResponseWithPk = await fixedContainer.CreateItemAsync( + item: itemWithPK); + Assert.AreEqual(HttpStatusCode.Created, createResponseWithPk.StatusCode); + + // Deleting item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. + ItemResponse deleteResponseWithoutPk = await fixedContainer.DeleteItemAsync( + partitionKey: Cosmos.PartitionKey.None, + id: activity.id); + Assert.AreEqual(HttpStatusCode.NoContent, deleteResponseWithoutPk.StatusCode); + } + } + + // Validate all items with no partition key value are returned + Assert.AreEqual(ItemsToCreate, resultsFetched); + + // Re-Query the items on the container with NonePartitionKeyValue + setIterator = fixedContainer.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() { MaxItemCount = ItemsToCreate, PartitionKey = Cosmos.PartitionKey.None, }); + + Assert.IsTrue(setIterator.HasMoreResults); + { + FeedResponse queryResponse = await setIterator.ReadNextAsync(); + Assert.AreEqual(0, queryResponse.Count()); + } + + // Query the items with newly inserted PartitionKey + setIterator = fixedContainer.GetItemQueryIterator( + sql, + requestOptions: new QueryRequestOptions() { MaxItemCount = ItemsToCreate + 1, PartitionKey = new Cosmos.PartitionKey("TestPK"), }); + + Assert.IsTrue(setIterator.HasMoreResults); + { + FeedResponse queryResponse = await setIterator.ReadNextAsync(); + Assert.AreEqual(ItemsToCreate, queryResponse.Count()); + } + } + finally + { + if (fixedContainer != null) + { + await fixedContainer.DeleteContainerStreamAsync(); + } + } + } + + + [TestMethod] + [DataRow(false)] + [DataRow(true)] + [TestCategory("Quarantine") /* Gated runs emulator as rate limiting disabled */] + public async Task VerifyToManyRequestTest(bool isQuery) + { + using CosmosClient client = TestCommon.CreateCosmosClient(); + Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync("LoadTest"); + Container container = await db.CreateContainerIfNotExistsAsync("LoadContainer", "/pk"); + + try + { + Task[] createItems = new Task[300]; + for (int i = 0; i < createItems.Length; i++) + { + ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity(); + createItems[i] = container.CreateItemStreamAsync( + partitionKey: new Cosmos.PartitionKey(temp.pk), + streamPayload: TestCommon.SerializerCore.ToStream(temp)); + } + + Task.WaitAll(createItems); + + List createQuery = new List(500); + List failedToManyRequests = new List(); + for (int i = 0; i < 500 && failedToManyRequests.Count == 0; i++) + { + createQuery.Add(VerifyQueryToManyExceptionAsync( + container, + isQuery, + failedToManyRequests)); + } + + Task[] tasks = createQuery.ToArray(); + Task.WaitAll(tasks); + + Assert.IsTrue(failedToManyRequests.Count > 0, "Rate limiting appears to be disabled"); + ResponseMessage failedResponseMessage = failedToManyRequests.First(); + Assert.AreEqual(failedResponseMessage.StatusCode, (HttpStatusCode)429); + Assert.IsNotNull(failedResponseMessage.ErrorMessage); + string diagnostics = failedResponseMessage.Diagnostics.ToString(); + Assert.IsNotNull(diagnostics); + } + finally + { + await db.DeleteStreamAsync(); + } + } + + [TestMethod] + public async Task VerifySessionTokenPassThrough() + { + ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity("TBD"); + + ItemResponse responseAstype = await this.Container.CreateItemAsync(partitionKey: new Cosmos.PartitionKey(temp.pk), item: temp); + + string sessionToken = responseAstype.Headers.Session; + Assert.IsNotNull(sessionToken); + + ResponseMessage readResponse = await this.Container.ReadItemStreamAsync(temp.id, new Cosmos.PartitionKey(temp.pk), new ItemRequestOptions() { SessionToken = sessionToken }); + + Assert.AreEqual(HttpStatusCode.OK, readResponse.StatusCode); + Assert.IsNotNull(readResponse.Headers.Session); + Assert.AreEqual(sessionToken, readResponse.Headers.Session); + } + + [TestMethod] + public async Task VerifySessionNotFoundStatistics() + { + using CosmosClient cosmosClient = TestCommon.CreateCosmosClient(new CosmosClientOptions() { ConsistencyLevel = Cosmos.ConsistencyLevel.Session }); + DatabaseResponse database = await cosmosClient.CreateDatabaseIfNotExistsAsync("NoSession"); + Container container = await database.Database.CreateContainerIfNotExistsAsync("NoSession", "/pk"); + + try + { + ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity("TBD"); + + ItemResponse responseAstype = await container.CreateItemAsync(partitionKey: new Cosmos.PartitionKey(temp.pk), item: temp); + + string invalidSessionToken = this.GetDifferentLSNToken(responseAstype.Headers.Session, 2000); + + try + { + ItemResponse readResponse = await container.ReadItemAsync(temp.id, new Cosmos.PartitionKey(temp.pk), new ItemRequestOptions() { SessionToken = invalidSessionToken }); + Assert.Fail("Should had thrown ReadSessionNotAvailable"); + } + catch (CosmosException cosmosException) + { + Assert.IsTrue(cosmosException.Message.Contains("The read session is not available for the input session token."), cosmosException.Message); + string exception = cosmosException.ToString(); + Assert.IsTrue(exception.Contains("Point Operation Statistics"), exception); + } + } + finally + { + await database.Database.DeleteStreamAsync(); + } + } + + private string GetDifferentLSNToken(string token, long lsnDifferent) + { + string[] tokenParts = token.Split(':'); + ISessionToken sessionToken = SessionTokenHelper.Parse(tokenParts[1]); + ISessionToken differentSessionToken = TestCommon.CreateSessionToken(sessionToken, sessionToken.LSN + lsnDifferent); + return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", tokenParts[0], differentSessionToken.ConvertToString()); + } + + /// + /// Stateless container re-create test. + /// Create two client instances and do meta data operations through a single client + /// but do all validation using both clients. + /// + [DataRow(true, true)] + [DataRow(true, false)] + [DataRow(false, true)] + [DataRow(false, false)] + [DataTestMethod] + public async Task ContainterReCreateStatelessTest(bool operationBetweenRecreate, bool isQuery) + { + Func operation; + if (isQuery) + { + operation = ExecuteQueryAsync; + } + else + { + operation = ExecuteReadFeedAsync; + } + + using CosmosClient cc1 = TestCommon.CreateCosmosClient(); + using CosmosClient cc2 = TestCommon.CreateCosmosClient(); + Cosmos.Database db1 = null; + try + { + string dbName = Guid.NewGuid().ToString(); + string containerName = Guid.NewGuid().ToString(); + + db1 = await cc1.CreateDatabaseAsync(dbName); + ContainerInternal container1 = (ContainerInlineCore)await db1.CreateContainerAsync(containerName, "/id"); + + await operation(container1, HttpStatusCode.OK); + + // Read through client2 -> return 404 + Container container2 = cc2.GetDatabase(dbName).GetContainer(containerName); + await operation(container2, HttpStatusCode.OK); + + // Delete container + await container1.DeleteContainerAsync(); + + if (operationBetweenRecreate) + { + // Read on deleted container through client1 + await operation(container1, HttpStatusCode.NotFound); + + // Read on deleted container through client2 + await operation(container2, HttpStatusCode.NotFound); + } + + // Re-create again + container1 = (ContainerInlineCore)await db1.CreateContainerAsync(containerName, "/id"); + + // Read through client1 + await operation(container1, HttpStatusCode.OK); + + // Read through client2 + await operation(container2, HttpStatusCode.OK); + } + finally + { + await db1.DeleteStreamAsync(); + cc1.Dispose(); + cc2.Dispose(); + } + } + + [TestMethod] + public async Task NoAutoGenerateIdTest() + { + try + { + ToDoActivity t = new ToDoActivity + { + pk = "AutoID" + }; + ItemResponse responseAstype = await this.Container.CreateItemAsync( + partitionKey: new Cosmos.PartitionKey(t.pk), item: t); + + Assert.Fail("Unexpected ID auto-generation"); + } + catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.BadRequest) + { + } + } + + [TestMethod] + public async Task AutoGenerateIdPatternTest() + { + ToDoActivity itemWithoutId = new ToDoActivity + { + pk = "AutoID" + }; + + ToDoActivity createdItem = await this.AutoGenerateIdPatternTest( + new Cosmos.PartitionKey(itemWithoutId.pk), itemWithoutId); + + Assert.IsNotNull(createdItem.id); + Assert.AreEqual(itemWithoutId.pk, createdItem.pk); + } + + [TestMethod] + [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task CustomPropertiesItemRequestOptionsTest(bool binaryEncodingEnabledInClient) + { + try { - toStreamCount = 0; - fromStreamCount = 0; - - List allItems = new List(); - int pageCount = 0; - using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( - queryDefinition: queryDefinition, - requestOptions: new QueryRequestOptions { MaxItemCount = 1 })) + if (binaryEncodingEnabledInClient) { - while (feedIterator.HasMoreResults) - { - // Only need once to verify correct serialization of the query definition - FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); - Assert.AreEqual(response.Count, response.Count()); - allItems.AddRange(response); - pageCount++; - } + Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, "True"); } - Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - - // There should be no call to custom serializer since the parameter values are already serialized. - Assert.AreEqual(0, toStreamCount, $"missing to stream call. Expected: 0 , Actual: {toStreamCount} for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - Assert.AreEqual(pageCount, fromStreamCount); - } - - - // Standard Cosmos Serializer Used + string customHeaderName = "custom-header1"; + string customHeaderValue = "value1"; - CosmosClient clientStandardSerializer = TestCommon.CreateCosmosClient(useCustomSeralizer: false); - Container containerStandardSerializer = clientStandardSerializer.GetContainer(this.database.Id, this.Container.Id); + CosmosClient clientWithIntercepter = TestCommon.CreateCosmosClient( + builder => builder.WithTransportClientHandlerFactory(transportClient => new TransportClientHelper.TransportClientWrapper( + transportClient, + (uri, resourceOperation, request) => + { + if (resourceOperation.resourceType == ResourceType.Document && + resourceOperation.operationType == OperationType.Create) + { + bool customHeaderExists = request.Properties.TryGetValue(customHeaderName, out object value); + + Assert.IsTrue(customHeaderExists); + Assert.AreEqual(customHeaderValue, value); + } + }))); - testItem1 = ToDoActivity.CreateRandomToDoActivity(); - testItem1.pk = "myPk"; - await containerStandardSerializer.CreateItemAsync(testItem1, new Cosmos.PartitionKey(testItem1.pk)); + Container container = clientWithIntercepter.GetContainer(this.database.Id, this.Container.Id); - testItem2 = ToDoActivity.CreateRandomToDoActivity(); - testItem2.pk = "myPk"; - await containerStandardSerializer.CreateItemAsync(testItem2, new Cosmos.PartitionKey(testItem2.pk)); - CosmosSerializer cosmosSerializer = containerStandardSerializer.Database.Client.ClientOptions.Serializer; + ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity("TBD"); - queryDefinitions = new List() - { - new QueryDefinition("select * from t where t.pk = @pk" ) - .WithParameterStream("@pk", cosmosSerializer.ToStream(testItem1.pk)), - new QueryDefinition("select * from t where t.cost = @cost" ) - .WithParameterStream("@cost", cosmosSerializer.ToStream(testItem1.cost)), - new QueryDefinition("select * from t where t.taskNum = @taskNum" ) - .WithParameterStream("@taskNum", cosmosSerializer.ToStream(testItem1.taskNum)), - new QueryDefinition("select * from t where t.CamelCase = @CamelCase" ) - .WithParameterStream("@CamelCase", cosmosSerializer.ToStream(testItem1.CamelCase)), - new QueryDefinition("select * from t where t.valid = @valid" ) - .WithParameterStream("@valid", cosmosSerializer.ToStream(testItem1.valid)), - new QueryDefinition("select * from t where t.description = @description" ) - .WithParameterStream("@description", cosmosSerializer.ToStream(testItem1.description)), - new QueryDefinition("select * from t where t.pk = @pk and t.cost = @cost" ) - .WithParameterStream("@pk", cosmosSerializer.ToStream(testItem1.pk)) - .WithParameterStream("@cost", cosmosSerializer.ToStream(testItem1.cost)), + Dictionary properties = new Dictionary() + { + { customHeaderName, customHeaderValue}, }; - foreach (QueryDefinition queryDefinition in queryDefinitions) - { - List allItems = new List(); - int pageCount = 0; - using (FeedIterator feedIterator = containerStandardSerializer.GetItemQueryIterator( - queryDefinition: queryDefinition)) - { - while (feedIterator.HasMoreResults) - { - // Only need once to verify correct serialization of the query definition - FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); - Assert.AreEqual(response.Count, response.Count()); - allItems.AddRange(response); - pageCount++; - } - } - - Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); - if (queryDefinition.QueryText.Contains("pk")) - { - Assert.AreEqual(1, pageCount); - } - else - { - Assert.AreEqual(3, pageCount); - } - - - - IReadOnlyList<(string Name, object Value)> parameters1 = queryDefinition.GetQueryParameters(); - IReadOnlyList<(string Name, object Value)> parameters2 = queryDefinition.GetQueryParameters(); - - Assert.AreSame(parameters1, parameters2); - } - } - - [TestMethod] - public async Task ItemIterator() - { - IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 3, randomPartitionKey: true); - HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); - FeedIterator feedIterator = - this.Container.GetItemQueryIterator(); - while (feedIterator.HasMoreResults) - { - foreach (ToDoActivity toDoActivity in await feedIterator.ReadNextAsync(this.cancellationToken)) - { - if (itemIds.Contains(toDoActivity.id)) - { - itemIds.Remove(toDoActivity.id); - } - } - } - - Assert.AreEqual(itemIds.Count, 0); - } - - [TestMethod] - public async Task PerfItemIterator() - { - IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 2000, randomPartitionKey: true); - HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); - - FeedIterator feedIterator = - this.Container.GetItemQueryIterator(); - while (feedIterator.HasMoreResults) - { - foreach (ToDoActivity toDoActivity in await feedIterator.ReadNextAsync(this.cancellationToken)) - { - if (itemIds.Contains(toDoActivity.id)) - { - itemIds.Remove(toDoActivity.id); - } - } - } - - Assert.AreEqual(itemIds.Count, 0); - } - - - [DataRow(1, 1)] - [DataRow(5, 5)] - [DataRow(6, 2)] - [DataTestMethod] - public async Task QuerySinglePartitionItemStreamTest(int perPKItemCount, int maxItemCount) - { - IList deleteList = deleteList = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 3, perPKItemCount: perPKItemCount, randomPartitionKey: true); - ToDoActivity find = deleteList.First(); - - QueryDefinition sql = new QueryDefinition("select * from r where r.pk = @pk").WithParameter("@pk", find.pk); - - int iterationCount = 0; - int totalReadItem = 0; - int expectedIterationCount = perPKItemCount / maxItemCount; - string lastContinuationToken = null; - - do - { - iterationCount++; - FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( - sql, - continuationToken: lastContinuationToken, - requestOptions: new QueryRequestOptions() - { - MaxItemCount = maxItemCount, - MaxConcurrency = 1, - PartitionKey = new Cosmos.PartitionKey(find.pk), - }); - - ResponseMessage response = await feedIterator.ReadNextAsync(); - lastContinuationToken = response.Headers.ContinuationToken; - Assert.AreEqual(response.ContinuationToken, response.Headers.ContinuationToken); - - System.Diagnostics.Trace.TraceInformation($"ContinuationToken: {lastContinuationToken}"); - Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); - - ServerSideCumulativeMetrics metrics = response.Diagnostics.GetQueryMetrics(); - Assert.IsTrue(metrics.PartitionedMetrics.Count > 0); - Assert.IsTrue(metrics.PartitionedMetrics[0].RequestCharge > 0); - Assert.IsTrue(metrics.CumulativeMetrics.TotalTime > TimeSpan.Zero); - Assert.IsTrue(metrics.CumulativeMetrics.QueryPreparationTime > TimeSpan.Zero); - Assert.IsTrue(metrics.TotalRequestCharge > 0); - - if (metrics.CumulativeMetrics.RetrievedDocumentCount >= 1) - { - Assert.IsTrue(metrics.CumulativeMetrics.RetrievedDocumentSize > 0); - Assert.IsTrue(metrics.CumulativeMetrics.DocumentLoadTime > TimeSpan.Zero); - Assert.IsTrue(metrics.CumulativeMetrics.RuntimeExecutionTime > TimeSpan.Zero); - } - else - { - Assert.AreEqual(0, metrics.CumulativeMetrics.RetrievedDocumentSize); - } - - using (StreamReader sr = new StreamReader(response.Content)) - using (JsonTextReader jtr = new JsonTextReader(sr)) - { - ToDoActivity[] results = serializer.Deserialize>(jtr).Data.ToArray(); - ToDoActivity[] readTodoActivities = results.OrderBy(e => e.id) - .ToArray(); - - ToDoActivity[] expectedTodoActivities = deleteList - .Where(e => e.pk == find.pk) - .Where(e => readTodoActivities.Any(e1 => e1.id == e.id)) - .OrderBy(e => e.id) - .ToArray(); - - totalReadItem += expectedTodoActivities.Length; - string expectedSerialized = JsonConvert.SerializeObject(expectedTodoActivities); - string readSerialized = JsonConvert.SerializeObject(readTodoActivities); - System.Diagnostics.Trace.TraceInformation($"Expected: {Environment.NewLine} {expectedSerialized}"); - System.Diagnostics.Trace.TraceInformation($"Read: {Environment.NewLine} {readSerialized}"); - - int count = results.Length; - Assert.AreEqual(maxItemCount, count); - - Assert.AreEqual(expectedSerialized, readSerialized); - - Assert.AreEqual(maxItemCount, expectedTodoActivities.Length); - } - } - while (lastContinuationToken != null); - - Assert.AreEqual(expectedIterationCount, iterationCount); - Assert.AreEqual(perPKItemCount, totalReadItem); - } - - /// - /// Validate multiple partition query - /// - [TestMethod] - public async Task ItemMultiplePartitionQuery() - { - IList itemList = await ToDoActivity.CreateRandomItems(this.Container, 3, randomPartitionKey: true); - - ToDoActivity find = itemList.First(); - QueryDefinition sql = new QueryDefinition("select * from toDoActivity t where t.id = '" + find.id + "'"); - - QueryRequestOptions requestOptions = new QueryRequestOptions() - { - MaxItemCount = 1, - MaxConcurrency = -1, - }; - - FeedIterator feedIterator = this.Container.GetItemQueryIterator( - sql, - requestOptions: requestOptions); - - bool found = false; - while (feedIterator.HasMoreResults) - { - FeedResponse iter = await feedIterator.ReadNextAsync(); - Assert.IsTrue(iter.Count() <= 1); - if (iter.Count() == 1) - { - found = true; - ToDoActivity response = iter.First(); - Assert.AreEqual(find.id, response.id); - } - - ServerSideCumulativeMetrics metrics = iter.Diagnostics.GetQueryMetrics(); - - if (metrics != null) - { - // This assumes that we are using parallel prefetch to hit multiple partitions concurrently - Assert.IsTrue(metrics.PartitionedMetrics.Count == 3); - Assert.IsTrue(metrics.CumulativeMetrics.TotalTime > TimeSpan.Zero); - Assert.IsTrue(metrics.CumulativeMetrics.QueryPreparationTime > TimeSpan.Zero); - Assert.IsTrue(metrics.TotalRequestCharge > 0); - - foreach (ServerSidePartitionedMetrics partitionedMetrics in metrics.PartitionedMetrics) - { - Assert.IsNotNull(partitionedMetrics); - Assert.IsNotNull(partitionedMetrics.FeedRange); - Assert.IsNotNull(partitionedMetrics.PartitionKeyRangeId); - Assert.IsTrue(partitionedMetrics.RequestCharge > 0); - } - - if (metrics.CumulativeMetrics.RetrievedDocumentCount >= 1) - { - Assert.IsTrue(metrics.CumulativeMetrics.RetrievedDocumentSize > 0); - Assert.IsTrue(metrics.CumulativeMetrics.DocumentLoadTime > TimeSpan.Zero); - Assert.IsTrue(metrics.CumulativeMetrics.RuntimeExecutionTime > TimeSpan.Zero); - } - else - { - Assert.AreEqual(0, metrics.CumulativeMetrics.RetrievedDocumentSize); - } - } - else - { - string diag = iter.Diagnostics.ToString(); - Assert.IsNotNull(diag); - } - } - - Assert.IsTrue(found); - } - - /// - /// Validate single partition query using gateway mode. - /// - [TestMethod] - public async Task ItemSinglePartitionQueryGateway() - { - ContainerResponse containerResponse = await this.database.CreateContainerAsync( - new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk")); - - Container createdContainer = (ContainerInlineCore)containerResponse; - CosmosClient client1 = TestCommon.CreateCosmosClient(useGateway: true); - - Container container = client1.GetContainer(this.database.Id, createdContainer.Id); - - string findId = "id2002"; - ToDoActivity item = ToDoActivity.CreateRandomToDoActivity("pk2002", findId); - await container.CreateItemAsync(item); - - QueryDefinition sql = new QueryDefinition("select * from toDoActivity t where t.id = '" + findId + "'"); - - QueryRequestOptions requestOptions = new QueryRequestOptions() - { - MaxBufferedItemCount = 10, - ResponseContinuationTokenLimitInKb = 500, - MaxItemCount = 1, - MaxConcurrency = 1, - }; - - FeedIterator feedIterator = container.GetItemQueryIterator( - sql, - requestOptions: requestOptions); - - bool found = false; - while (feedIterator.HasMoreResults) - { - FeedResponse iter = await feedIterator.ReadNextAsync(); - Assert.IsTrue(iter.Count() <= 1); - if (iter.Count() == 1) - { - found = true; - ToDoActivity response = iter.First(); - Assert.AreEqual(findId, response.id); - } - - ServerSideCumulativeMetrics metrics = iter.Diagnostics.GetQueryMetrics(); - - if (metrics != null) - { - Assert.IsTrue(metrics.PartitionedMetrics.Count == 1); - Assert.IsTrue(metrics.CumulativeMetrics.TotalTime > TimeSpan.Zero); - Assert.IsTrue(metrics.CumulativeMetrics.QueryPreparationTime > TimeSpan.Zero); - Assert.IsTrue(metrics.TotalRequestCharge > 0); - - foreach (ServerSidePartitionedMetrics partitionedMetrics in metrics.PartitionedMetrics) - { - Assert.IsNotNull(partitionedMetrics); - Assert.IsNotNull(partitionedMetrics.FeedRange); - Assert.IsNull(partitionedMetrics.PartitionKeyRangeId); - Assert.IsTrue(partitionedMetrics.RequestCharge > 0); - } - - if (metrics.CumulativeMetrics.RetrievedDocumentCount >= 1) - { - Assert.IsTrue(metrics.CumulativeMetrics.RetrievedDocumentSize > 0); - Assert.IsTrue(metrics.CumulativeMetrics.DocumentLoadTime > TimeSpan.Zero); - Assert.IsTrue(metrics.CumulativeMetrics.RuntimeExecutionTime > TimeSpan.Zero); - } - else - { - Assert.AreEqual(0, metrics.CumulativeMetrics.RetrievedDocumentSize); - } - } - } - - Assert.IsTrue(found); - } - - /// - /// Validate multiple partition query - /// - [TestMethod] - public async Task ItemMultiplePartitionOrderByQueryStream() - { - CultureInfo defaultCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture; - - CultureInfo[] cultureInfoList = new CultureInfo[] - { - defaultCultureInfo, - System.Globalization.CultureInfo.GetCultureInfo("fr-FR") - }; - - IList deleteList = await ToDoActivity.CreateRandomItems( - this.Container, - 300, - randomPartitionKey: true, - randomTaskNumber: true); - - try - { - foreach (CultureInfo cultureInfo in cultureInfoList) - { - System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; - - QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t ORDER BY t.taskNum "); - - QueryRequestOptions requestOptions = new QueryRequestOptions() - { - MaxBufferedItemCount = 10, - ResponseContinuationTokenLimitInKb = 500, - MaxConcurrency = 5, - MaxItemCount = 1, - }; - - List resultList = new List(); - double totalRequstCharge = 0; - FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( - sql, - requestOptions: requestOptions); - - while (feedIterator.HasMoreResults) - { - ResponseMessage iter = await feedIterator.ReadNextAsync(); - Assert.IsTrue(iter.IsSuccessStatusCode); - Assert.IsNull(iter.ErrorMessage); - totalRequstCharge += iter.Headers.RequestCharge; - - ToDoActivity[] activities = TestCommon.SerializerCore.FromStream>(iter.Content).Data.ToArray(); - Assert.AreEqual(1, activities.Length); - ToDoActivity response = activities.First(); - resultList.Add(response); - } - - Assert.AreEqual(deleteList.Count, resultList.Count); - Assert.IsTrue(totalRequstCharge > 0); - - List verifiedOrderBy = deleteList.OrderBy(x => x.taskNum).ToList(); - for (int i = 0; i < verifiedOrderBy.Count(); i++) - { - Assert.AreEqual(verifiedOrderBy[i].taskNum, resultList[i].taskNum); - Assert.AreEqual(verifiedOrderBy[i].id, resultList[i].id); - } - } - } - finally - { - System.Threading.Thread.CurrentThread.CurrentCulture = defaultCultureInfo; - } - } - - /// - /// Validate multiple partition query - /// - [TestMethod] - public async Task ItemMultiplePartitionQueryStream() - { - IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 101, randomPartitionKey: true); - QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t"); - - QueryRequestOptions requestOptions = new QueryRequestOptions() - { - MaxConcurrency = 5, - MaxItemCount = 5, - }; - - List resultList = new List(); - double totalRequstCharge = 0; - FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator(sql, requestOptions: requestOptions); - while (feedIterator.HasMoreResults) - { - ResponseMessage iter = await feedIterator.ReadNextAsync(); - Assert.IsTrue(iter.IsSuccessStatusCode); - Assert.IsNull(iter.ErrorMessage); - totalRequstCharge += iter.Headers.RequestCharge; - ToDoActivity[] response = TestCommon.SerializerCore.FromStream>(iter.Content).Data.ToArray(); - Assert.IsTrue(response.Length <= 5); - resultList.AddRange(response); - } - - Assert.AreEqual(deleteList.Count, resultList.Count); - Assert.IsTrue(totalRequstCharge > 0); - - List verifiedOrderBy = deleteList.OrderBy(x => x.id).ToList(); - resultList = resultList.OrderBy(x => x.id).ToList(); - for (int i = 0; i < verifiedOrderBy.Count(); i++) - { - Assert.AreEqual(verifiedOrderBy[i].taskNum, resultList[i].taskNum); - Assert.AreEqual(verifiedOrderBy[i].id, resultList[i].id); - } - } - - /// - /// Validate multiple partition query - /// - [TestMethod] - public async Task ItemSinglePartitionQueryStream() - { - //Create a 101 random items with random guid PK values - IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 101, perPKItemCount: 1, randomPartitionKey: true); - - // Create 10 items with same pk value - IList findItems = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 1, perPKItemCount: 10, randomPartitionKey: false); - - string findPkValue = findItems.First().pk; - QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t where t.pk = @pkValue").WithParameter("@pkValue", findPkValue); - - - double totalRequstCharge = 0; - FeedIterator setIterator = this.Container.GetItemQueryStreamIterator( - sql, - requestOptions: new QueryRequestOptions() - { - MaxConcurrency = 1, - PartitionKey = new Cosmos.PartitionKey(findPkValue), - }); - - List foundItems = new List(); - while (setIterator.HasMoreResults) - { - ResponseMessage iter = await setIterator.ReadNextAsync(); - Assert.IsTrue(iter.IsSuccessStatusCode); - Assert.IsNull(iter.ErrorMessage); - totalRequstCharge += iter.Headers.RequestCharge; - Collection response = TestCommon.SerializerCore.FromStream>(iter.Content).Data; - foundItems.AddRange(response); - } - - Assert.AreEqual(findItems.Count, foundItems.Count); - Assert.IsFalse(foundItems.Any(x => !string.Equals(x.pk, findPkValue)), "All the found items should have the same PK value"); - Assert.IsTrue(totalRequstCharge > 0); - } - - [TestMethod] - public async Task EpkPointReadTest() - { - string pk = Guid.NewGuid().ToString(); - string epk = new PartitionKey(pk) - .InternalKey - .GetEffectivePartitionKeyString(this.containerSettings.PartitionKey); - - Dictionary properties = new Dictionary() - { - { WFConstants.BackendHeaders.EffectivePartitionKeyString, epk }, - }; - - ItemRequestOptions itemRequestOptions = new ItemRequestOptions - { - IsEffectivePartitionKeyRouting = true, - Properties = properties, - }; - - ResponseMessage response = await this.Container.ReadItemStreamAsync( - Guid.NewGuid().ToString(), - Cosmos.PartitionKey.Null, - itemRequestOptions); - - // Ideally it should be NotFound - // BadReqeust bcoz collection is regular and not binary - Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); - - await this.Container.CreateItemAsync(new { id = Guid.NewGuid().ToString(), pk = "test" }); - epk = new PartitionKey("test") - .InternalKey - .GetEffectivePartitionKeyString(this.containerSettings.PartitionKey); - properties = new Dictionary() - { - { WFConstants.BackendHeaders.EffectivePartitionKeyString, epk }, - }; - - QueryRequestOptions queryRequestOptions = new QueryRequestOptions - { - IsEffectivePartitionKeyRouting = true, - Properties = properties, - }; - - using (FeedIterator resultSet = this.Container.GetItemQueryIterator( - queryText: "SELECT * FROM root", - requestOptions: queryRequestOptions)) - { - FeedResponse feedresponse = await resultSet.ReadNextAsync(); - Assert.IsNotNull(feedresponse.Resource); - Assert.AreEqual(1, feedresponse.Count()); - } - - } - - /// - /// Validate that if the EPK is set in the options that only a single range is selected. - /// - [TestMethod] - public async Task ItemEpkQuerySingleKeyRangeValidation() - { - ContainerInternal container = null; - try - { - // Create a container large enough to have at least 2 partitions - ContainerResponse containerResponse = await this.database.CreateContainerAsync( - id: Guid.NewGuid().ToString(), - partitionKeyPath: "/pk", - throughput: 15000); - container = (ContainerInlineCore)containerResponse; - - // Get all the partition key ranges to verify there is more than one partition - IRoutingMapProvider routingMapProvider = await this.GetClient().DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton); - IReadOnlyList ranges = await routingMapProvider.TryGetOverlappingRangesAsync( - containerResponse.Resource.ResourceId, - new Documents.Routing.Range("00", "FF", isMaxInclusive: true, isMinInclusive: true), - NoOpTrace.Singleton, - forceRefresh: false); - - // If this fails the RUs of the container needs to be increased to ensure at least 2 partitions. - Assert.IsTrue(ranges.Count > 1, " RUs of the container needs to be increased to ensure at least 2 partitions."); - - - ContainerQueryProperties containerQueryProperties = new ContainerQueryProperties( - containerResponse.Resource.ResourceId, - effectivePartitionKeyRanges: null, - //new List> { new Documents.Routing.Range("AA", "AA", true, true) }, - containerResponse.Resource.PartitionKey, - vectorEmbeddingPolicy: null, - containerResponse.Resource.GeospatialConfig.GeospatialType); - - // There should only be one range since the EPK option is set. - List partitionKeyRanges = await CosmosQueryExecutionContextFactory.GetTargetPartitionKeyRangesAsync( - queryClient: new CosmosQueryClientCore(container.ClientContext, container), - resourceLink: container.LinkUri, - partitionedQueryExecutionInfo: null, - containerQueryProperties: containerQueryProperties, - properties: new Dictionary() - { - {"x-ms-effective-partition-key-string", "AA" } - }, - feedRangeInternal: null, - trace: NoOpTrace.Singleton); - - Assert.IsTrue(partitionKeyRanges.Count == 1, "Only 1 partition key range should be selected since the EPK option is set."); - - } - finally - { - if (container != null) - { - await container.DeleteContainerStreamAsync(); - } - } - } - - /// - /// Validate multiple partition query - /// - [TestMethod] - public async Task ItemQueryStreamSerializationSetting() - { - IList deleteList = await ToDoActivity.CreateRandomItems( - container: this.Container, - pkCount: 101, - randomTaskNumber: true); - - QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t ORDER BY t.taskNum"); - - CosmosSerializationFormatOptions options = new CosmosSerializationFormatOptions( - ContentSerializationFormat.CosmosBinary.ToString(), - (content) => JsonNavigator.Create(content), - () => JsonWriter.Create(JsonSerializationFormat.Binary)); - - QueryRequestOptions requestOptions = new QueryRequestOptions() - { - CosmosSerializationFormatOptions = options, - MaxConcurrency = 5, - MaxItemCount = 5, - }; - - List resultList = new List(); - double totalRequstCharge = 0; - FeedIterator feedIterator = this.Container.GetItemQueryStreamIterator( - sql, - requestOptions: requestOptions); - - while (feedIterator.HasMoreResults) - { - ResponseMessage response = await feedIterator.ReadNextAsync(); - Assert.IsTrue(response.IsSuccessStatusCode); - Assert.IsNull(response.ErrorMessage); - totalRequstCharge += response.Headers.RequestCharge; - - //Copy the stream and check that the first byte is the correct value - MemoryStream memoryStream = new MemoryStream(); - response.Content.CopyTo(memoryStream); - byte[] content = memoryStream.ToArray(); - response.Content.Position = 0; - - // Examine the first buffer byte to determine the serialization format - byte firstByte = content[0]; - Assert.AreEqual(128, firstByte); - Assert.AreEqual(JsonSerializationFormat.Binary, (JsonSerializationFormat)firstByte); - - IJsonReader reader = JsonReader.Create(content); - IJsonWriter textWriter = JsonWriter.Create(JsonSerializationFormat.Text); - reader.WriteAll(textWriter); - string json = Encoding.UTF8.GetString(textWriter.GetResult().ToArray()); - Assert.IsNotNull(json); - ToDoActivity[] responseActivities = JsonConvert.DeserializeObject>(json).Data.ToArray(); - Assert.IsTrue(responseActivities.Length <= 5); - resultList.AddRange(responseActivities); - } - - Assert.AreEqual(deleteList.Count, resultList.Count); - Assert.IsTrue(totalRequstCharge > 0); - - List verifiedOrderBy = deleteList.OrderBy(x => x.taskNum).ToList(); - for (int i = 0; i < verifiedOrderBy.Count(); i++) - { - Assert.AreEqual(verifiedOrderBy[i].taskNum, resultList[i].taskNum); - Assert.AreEqual(verifiedOrderBy[i].id, resultList[i].id); - } - } - - /// - /// Validate that the max item count works correctly. - /// - /// - [TestMethod] - public async Task ValidateMaxItemCountOnItemQuery() - { - IList deleteList = await ToDoActivity.CreateRandomItems(container: this.Container, pkCount: 1, perPKItemCount: 6, randomPartitionKey: false); - - ToDoActivity toDoActivity = deleteList.First(); - QueryDefinition sql = new QueryDefinition( - "select * from toDoActivity t where t.pk = @pk") - .WithParameter("@pk", toDoActivity.pk); - - // Test max size at 1 - FeedIterator feedIterator = this.Container.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() - { - MaxItemCount = 1, - PartitionKey = new Cosmos.PartitionKey(toDoActivity.pk), - }); - - while (feedIterator.HasMoreResults) - { - FeedResponse iter = await feedIterator.ReadNextAsync(); - Assert.AreEqual(1, iter.Count()); - } - - // Test max size at 2 - FeedIterator setIteratorMax2 = this.Container.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() - { - MaxItemCount = 2, - PartitionKey = new Cosmos.PartitionKey(toDoActivity.pk), - }); - - while (setIteratorMax2.HasMoreResults) - { - FeedResponse iter = await setIteratorMax2.ReadNextAsync(); - Assert.AreEqual(2, iter.Count()); - } - } - - /// - /// Validate that the max item count works correctly. - /// - /// - [TestMethod] - public async Task NegativeQueryTest() - { - await ToDoActivity.CreateRandomItems(container: this.Container, pkCount: 10, perPKItemCount: 20, randomPartitionKey: true); - - try - { - using (FeedIterator resultSet = this.Container.GetItemQueryIterator( - queryText: "SELECT r.id FROM root r WHERE r._ts > 0", - requestOptions: new QueryRequestOptions() - { - ResponseContinuationTokenLimitInKb = 0, - MaxItemCount = 10, - MaxConcurrency = 1 - })) - { - await resultSet.ReadNextAsync(); - } - Assert.Fail("Expected query to fail"); - } - catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) - { - Assert.IsTrue(exception.Message.Contains("continuation token limit specified is not large enough"), exception.Message); - } - - try - { - using (FeedIterator resultSet = this.Container.GetItemQueryIterator( - queryText: "SELECT r.id FROM root r WHERE r._ts >!= 0", - requestOptions: new QueryRequestOptions() { MaxConcurrency = 1 })) - { - await resultSet.ReadNextAsync(); - } - Assert.Fail("Expected query to fail"); - } - catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) - { - Assert.IsTrue(exception.Message.Contains("Syntax error, incorrect syntax near"), exception.Message); - } - } - - [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task ItemRequestOptionAccessConditionTest(bool binaryEncodingEnabledInClient) - { - try - { - if (binaryEncodingEnabledInClient) - { - Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, "True"); - } - - // Create an item - ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); - - ItemRequestOptions itemRequestOptions = new ItemRequestOptions() - { - IfMatchEtag = Guid.NewGuid().ToString(), - }; - - using (ResponseMessage responseMessage = await this.Container.UpsertItemStreamAsync( - streamPayload: TestCommon.SerializerCore.ToStream(testItem), - partitionKey: new Cosmos.PartitionKey(testItem.pk), - requestOptions: itemRequestOptions)) - { - Assert.IsNotNull(responseMessage); - Assert.IsNull(responseMessage.Content); - Assert.AreEqual(HttpStatusCode.PreconditionFailed, responseMessage.StatusCode, responseMessage.ErrorMessage); - Assert.AreNotEqual(responseMessage.Headers.ActivityId, Guid.Empty); - Assert.IsTrue(responseMessage.Headers.RequestCharge > 0); - Assert.IsFalse(string.IsNullOrEmpty(responseMessage.ErrorMessage)); - Assert.IsTrue(responseMessage.ErrorMessage.Contains("One of the specified pre-condition is not met")); - } - - try - { - ItemResponse response = await this.Container.UpsertItemAsync( - item: testItem, - requestOptions: itemRequestOptions); - Assert.Fail("Access condition should have failed"); - } - catch (CosmosException e) - { - Assert.IsNotNull(e); - Assert.AreEqual(HttpStatusCode.PreconditionFailed, e.StatusCode, e.Message); - Assert.AreNotEqual(e.ActivityId, Guid.Empty); - Assert.IsTrue(e.RequestCharge > 0); - string expectedResponseBody = $"{Environment.NewLine}Errors : [{Environment.NewLine} \"One of the specified pre-condition is not met. Learn more: https://aka.ms/CosmosDB/sql/errors/precondition-failed\"{Environment.NewLine}]{Environment.NewLine}"; - Assert.AreEqual(expectedResponseBody, e.ResponseBody); - string expectedMessage = $"Response status code does not indicate success: PreconditionFailed (412); Substatus: 0; ActivityId: {e.ActivityId}; Reason: ({expectedResponseBody});"; - Assert.AreEqual(expectedMessage, e.Message); - } - finally - { - ItemResponse deleteResponse = await this.Container.DeleteItemAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id); - Assert.IsNotNull(deleteResponse); - } - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - - [TestMethod] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - public async Task ItemReplaceAsyncTest(bool binaryEncodingEnabledInClient) - { - try - { - if (binaryEncodingEnabledInClient) - { - Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, "True"); - } - - // Create an item - ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); - - string originalId = testItem.id; - testItem.id = Guid.NewGuid().ToString(); - - ItemResponse response = await this.Container.ReplaceItemAsync( - id: originalId, - item: testItem); - - Assert.AreEqual(testItem.id, response.Resource.id); - Assert.AreNotEqual(originalId, response.Resource.id); - - string originalStatus = testItem.pk; - testItem.pk = Guid.NewGuid().ToString(); - - try - { - response = await this.Container.ReplaceItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(originalStatus), - item: testItem); - Assert.Fail("Replace changing partition key is not supported."); - } - catch (CosmosException ce) - { - Assert.AreEqual((HttpStatusCode)400, ce.StatusCode); - } - } - finally - { - Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - - [TestMethod] - public async Task ItemPatchFailureTest() - { - // Create an item - ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); - ContainerInternal containerInternal = (ContainerInternal)this.Container; - - List patchOperations = new List - { - PatchOperation.Add("/nonExistentParent/Child", "bar"), - PatchOperation.Remove("/cost") - }; - - // item does not exist - 404 Resource Not Found error - try - { - await containerInternal.PatchItemAsync( - id: Guid.NewGuid().ToString(), - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.Fail("Patch operation should fail if the item doesn't exist."); - } - catch (CosmosException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode); - Assert.IsTrue(ex.Message.Contains("Resource Not Found")); - Assert.IsTrue(ex.Message.Contains("https://aka.ms/cosmosdb-tsg-not-found")); - CosmosItemTests.ValidateCosmosException(ex); - } - - // adding a child when parent / ancestor does not exist - 400 BadRequest response - try - { - await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.Fail("Patch operation should fail for malformed PatchSpecification."); - } - catch (CosmosException ex) - { - Assert.AreEqual(HttpStatusCode.BadRequest, ex.StatusCode); - Assert.IsTrue(ex.Message.Contains(@"For Operation(1): Add Operation can only create a child object of an existing node(array or object) and cannot create path recursively, no path found beyond: 'nonExistentParent'. Learn more: https://aka.ms/cosmosdbpatchdocs"), ex.Message); - CosmosItemTests.ValidateCosmosException(ex); - } - - // precondition failure - 412 response - PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() - { - IfMatchEtag = Guid.NewGuid().ToString() - }; - - try - { - await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations, - requestOptions); - - Assert.Fail("Patch operation should fail in case of pre-condition failure."); - } - catch (CosmosException ex) - { - Assert.AreEqual(HttpStatusCode.PreconditionFailed, ex.StatusCode); - Assert.IsTrue(ex.Message.Contains("One of the specified pre-condition is not met")); - CosmosItemTests.ValidateCosmosException(ex); - } - } - - [TestMethod] - public async Task ItemPatchSuccessTest() - { - // Create an item - ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(this.Container, 1, randomPartitionKey: true)).First(); - ContainerInternal containerInternal = (ContainerInternal)this.Container; - - int originalTaskNum = testItem.taskNum; - int newTaskNum = originalTaskNum + 1; - //Int16 one = 1; - - Assert.IsNull(testItem.children[1].pk); - List patchOperations = new List() - { - PatchOperation.Set("/children/0/description", "testSet"), - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Replace("/taskNum", newTaskNum), - //PatchOperation.Increment("/taskNum", one) - - PatchOperation.Set("/children/1/nullableInt",null) - }; - - // without content response - PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() - { - EnableContentResponseOnWrite = false - }; - - ItemResponse response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations, - requestOptions); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNull(response.Resource); - - // read resource to validate the patch operation - response = await containerInternal.ReadItemAsync( - testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk)); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - Assert.AreEqual("testSet", response.Resource.children[0].description); - Assert.AreEqual("patched", response.Resource.children[1].pk); - Assert.IsNull(response.Resource.description); - Assert.AreEqual(newTaskNum, response.Resource.taskNum); - Assert.IsNull(response.Resource.children[1].nullableInt); - - patchOperations.Clear(); - patchOperations.Add(PatchOperation.Add("/children/0/cost", 1)); - // with content response - response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - Assert.AreEqual(1, response.Resource.children[0].cost); - - patchOperations.Clear(); - patchOperations.Add(PatchOperation.Set("/children/0/id", null)); - // with content response - response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - Assert.AreEqual(null, response.Resource.children[0].id); - - patchOperations.Clear(); - patchOperations.Add(PatchOperation.Add("/children/1/description", "Child#1")); - patchOperations.Add(PatchOperation.Move("/children/0/description", "/description")); - patchOperations.Add(PatchOperation.Move("/children/1/description", "/children/0/description")); - // with content response - response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - Assert.AreEqual("testSet", response.Resource.description); - Assert.AreEqual("Child#1", response.Resource.children[0].description); - Assert.IsNull(response.Resource.children[1].description); - } - - [TestMethod] - public async Task PatchItemStreamTest() - { - ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); - ContainerInternal containerInternal = (ContainerInternal)this.Container; - - List patchOperations = new List() - { - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Replace("/taskNum", testItem.taskNum+1) - }; - - PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() - { - FilterPredicate = "from root where root.x = 3" - }; - - // Patch a non-existing item. It should fail, and not throw an exception. - using (ResponseMessage response = await containerInternal.PatchItemStreamAsync( - partitionKey: new Cosmos.PartitionKey(testItem.pk), - id: testItem.id, - patchOperations: patchOperations, - requestOptions: requestOptions)) - { - Assert.IsFalse(response.IsSuccessStatusCode); - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode, response.ErrorMessage); - } - - using (Stream stream = TestCommon.SerializerCore.ToStream(testItem)) - { - // Create the item - using (ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), streamPayload: stream)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); - } - } - - // Patch - using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperations)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } - - // Read and validate - ItemResponse itemResponse = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); - Assert.AreEqual(HttpStatusCode.OK, itemResponse.StatusCode); - Assert.IsNotNull(itemResponse.Resource); - Assert.AreEqual("patched", itemResponse.Resource.children[1].pk); - Assert.IsNull(itemResponse.Resource.description); - Assert.AreEqual(testItem.taskNum + 1, itemResponse.Resource.taskNum); - - // Delete - using (ResponseMessage deleteResponse = await this.Container.DeleteItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id)) - { - Assert.IsNotNull(deleteResponse); - Assert.AreEqual(deleteResponse.StatusCode, HttpStatusCode.NoContent); - } - } - - [TestMethod] - public async Task ContainerRecreateScenarioGatewayTest() - { - ContainerResponse response = await this.database.CreateContainerAsync( - new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/pk")); - - Container createdContainer = (ContainerInlineCore)response; - - CosmosClient client1 = TestCommon.CreateCosmosClient(useGateway: true); - CosmosClient client2 = TestCommon.CreateCosmosClient(useGateway: true); - - Container container1 = client1.GetContainer(this.database.Id, createdContainer.Id); - Container container2 = client2.GetContainer(this.database.Id, createdContainer.Id); - Cosmos.Database database2 = client2.GetDatabase(this.database.Id); - - ToDoActivity item = ToDoActivity.CreateRandomToDoActivity("pk2002", "id2002"); - await container1.CreateItemAsync(item); - - await container2.DeleteContainerAsync(); - await database2.CreateContainerAsync(createdContainer.Id, "/pk"); - - container2 = database2.GetContainer(this.Container.Id); - await container2.CreateItemAsync(item); - - // should not throw exception - await this.Container.ReadItemAsync("id2002", new Cosmos.PartitionKey("pk2002")); - } - - [TestMethod] - public async Task BatchPatchConditionTest() - { - ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); - ContainerInternal containerInternal = (ContainerInternal)this.Container; - - using (Stream stream = TestCommon.SerializerCore.ToStream(testItem)) - { - // Create the item - using (ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), streamPayload: stream)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); - } - } - - List patchOperations = new List() - { - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Add("/taskNum", 8) - }; - - using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperations)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } - - List patchOperationsUpdateTaskNum12 = new List() - { - PatchOperation.Replace("/taskNum", 12) - }; - - TransactionalBatchPatchItemRequestOptions requestOptionsFalse = new TransactionalBatchPatchItemRequestOptions() - { - FilterPredicate = "from c where c.taskNum = 3" - }; - - TransactionalBatchInternal transactionalBatchInternalFalse = (TransactionalBatchInternal)containerInternal.CreateTransactionalBatch(new Cosmos.PartitionKey(testItem.pk)); - transactionalBatchInternalFalse.PatchItem(id: testItem.id, patchOperationsUpdateTaskNum12, requestOptionsFalse); - using (TransactionalBatchResponse batchResponse = await transactionalBatchInternalFalse.ExecuteAsync()) - { - Assert.IsNotNull(batchResponse); - Assert.AreEqual(HttpStatusCode.PreconditionFailed, batchResponse.StatusCode); - } - - { - // Read and validate - ItemResponse itemResponsemid = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); - Assert.AreEqual(HttpStatusCode.OK, itemResponsemid.StatusCode); - Assert.IsNotNull(itemResponsemid.Resource); - Assert.AreEqual("patched", itemResponsemid.Resource.children[1].pk); - Assert.IsNull(itemResponsemid.Resource.description); - Assert.AreEqual(8, itemResponsemid.Resource.taskNum); - } - - List patchOperationsUpdateTaskNum14 = new List() - { - PatchOperation.Increment("/taskNum", 6) - }; - - TransactionalBatchPatchItemRequestOptions requestOptionsTrue = new TransactionalBatchPatchItemRequestOptions() - { - FilterPredicate = "from root where root.taskNum = 8" - }; - - TransactionalBatchInternal transactionalBatchInternalTrue = (TransactionalBatchInternal)containerInternal.CreateTransactionalBatch(new Cosmos.PartitionKey(testItem.pk)); - transactionalBatchInternalTrue.PatchItem(id: testItem.id, patchOperationsUpdateTaskNum14, requestOptionsTrue); - using (TransactionalBatchResponse batchResponse = await transactionalBatchInternalTrue.ExecuteAsync()) - { - Assert.IsNotNull(batchResponse); - Assert.AreEqual(HttpStatusCode.OK, batchResponse.StatusCode); - } - - // Read and validate - ItemResponse itemResponse = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); - Assert.AreEqual(HttpStatusCode.OK, itemResponse.StatusCode); - Assert.IsNotNull(itemResponse.Resource); - Assert.AreEqual("patched", itemResponse.Resource.children[1].pk); - Assert.IsNull(itemResponse.Resource.description); - Assert.AreEqual(14, itemResponse.Resource.taskNum); - - // Delete - using (ResponseMessage deleteResponse = await this.Container.DeleteItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id)) - { - Assert.IsNotNull(deleteResponse); - Assert.AreEqual(deleteResponse.StatusCode, HttpStatusCode.NoContent); - } - } - - [TestMethod] - public async Task PatchConditionTest() - { - ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); - ContainerInternal containerInternal = (ContainerInternal)this.Container; - - using (Stream stream = TestCommon.SerializerCore.ToStream(testItem)) - { - // Create the item - using (ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), streamPayload: stream)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); - } - } - - List patchOperations = new List() - { - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Add("/taskNum", 8) - }; - - // Patch - using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperations)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } - - List patchOperationsUpdateTaskNum12 = new List() - { - PatchOperation.Replace("/taskNum", 12) - }; - - PatchItemRequestOptions requestOptionsFalse = new PatchItemRequestOptions() - { - FilterPredicate = "from c where c.taskNum = 3" - }; - - // Patch that fails due to condition not met. - using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperationsUpdateTaskNum12, requestOptions: requestOptionsFalse)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.PreconditionFailed, response.StatusCode); - } - - { - // Read and validate - ItemResponse itemResponsemid = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); - Assert.AreEqual(HttpStatusCode.OK, itemResponsemid.StatusCode); - Assert.IsNotNull(itemResponsemid.Resource); - Assert.AreEqual("patched", itemResponsemid.Resource.children[1].pk); - Assert.IsNull(itemResponsemid.Resource.description); - Assert.AreEqual(8, itemResponsemid.Resource.taskNum); - } - - List patchOperationsUpdateTaskNum14 = new List() - { - PatchOperation.Increment("/taskNum", 6) - }; - - PatchItemRequestOptions requestOptionsTrue = new PatchItemRequestOptions() - { - FilterPredicate = "from root where root.taskNum = 8" - }; - - // Patch - using (ResponseMessage response = await containerInternal.PatchItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id, patchOperations: patchOperationsUpdateTaskNum14, requestOptions: requestOptionsTrue)) - { - Assert.IsNotNull(response); - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } - - // Read and validate - ItemResponse itemResponse = await this.Container.ReadItemAsync(testItem.id, partitionKey: new Cosmos.PartitionKey(testItem.pk)); - Assert.AreEqual(HttpStatusCode.OK, itemResponse.StatusCode); - Assert.IsNotNull(itemResponse.Resource); - Assert.AreEqual("patched", itemResponse.Resource.children[1].pk); - Assert.IsNull(itemResponse.Resource.description); - Assert.AreEqual(14, itemResponse.Resource.taskNum); - - // Delete - using (ResponseMessage deleteResponse = await this.Container.DeleteItemStreamAsync(partitionKey: new Cosmos.PartitionKey(testItem.pk), id: testItem.id)) - { - Assert.IsNotNull(deleteResponse); - Assert.AreEqual(deleteResponse.StatusCode, HttpStatusCode.NoContent); - } - } - - [TestMethod] - public async Task ItemPatchViaGatewayTest() - { - CosmosClient gatewayClient = TestCommon.CreateCosmosClient(useGateway: true); - Container gatewayContainer = gatewayClient.GetContainer(this.database.Id, this.Container.Id); - ContainerInternal containerInternal = (ContainerInternal)gatewayContainer; - - // Create an item - ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(gatewayContainer, 1, randomPartitionKey: true)).First(); - - int originalTaskNum = testItem.taskNum; - int newTaskNum = originalTaskNum + 1; - - Assert.IsNull(testItem.children[1].pk); - - List patchOperations = new List() - { - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Replace("/taskNum", newTaskNum) - }; - - ItemResponse response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - Assert.AreEqual("patched", response.Resource.children[1].pk); - Assert.IsNull(response.Resource.description); - Assert.AreEqual(newTaskNum, response.Resource.taskNum); - } - - [TestMethod] - public async Task ItemPatchCustomSerializerTest() - { - CosmosClientOptions clientOptions = new CosmosClientOptions() - { - Serializer = new CosmosJsonDotNetSerializer( - new JsonSerializerSettings() - { - DateFormatString = "dd / MM / yy hh:mm" - }) - }; - - CosmosClient customSerializationClient = TestCommon.CreateCosmosClient(clientOptions); - Container customSerializationContainer = customSerializationClient.GetContainer(this.database.Id, this.Container.Id); - ContainerInternal containerInternal = (ContainerInternal)customSerializationContainer; - - ToDoActivity testItem = (await ToDoActivity.CreateRandomItems(customSerializationContainer, 1, randomPartitionKey: true)).First(); - - PatchItemRequestOptions requestOptions = new PatchItemRequestOptions() - { - EnableContentResponseOnWrite = false - }; - - DateTime patchDate = new DateTime(2020, 07, 01, 01, 02, 03); - Stream patchDateStreamInput = new CosmosJsonDotNetSerializer().ToStream(patchDate); - string streamDateJson; - using (Stream stream = new MemoryStream()) - { - patchDateStreamInput.CopyTo(stream); - stream.Position = 0; - patchDateStreamInput.Position = 0; - using (StreamReader streamReader = new StreamReader(stream)) - { - streamDateJson = streamReader.ReadToEnd(); - } - } - - List patchOperations = new List() - { - PatchOperation.Add("/date", patchDate), - PatchOperation.Add("/dateStream", patchDateStreamInput) - }; - - ItemResponse response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations, - requestOptions); - - JsonSerializerSettings jsonSettings = new JsonSerializerSettings(); - jsonSettings.DateFormatString = "dd / MM / yy hh:mm"; - string dateJson = JsonConvert.SerializeObject(patchDate, jsonSettings); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - - // regular container - response = await this.Container.ReadItemAsync( - testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk)); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - Assert.IsTrue(dateJson.Contains(response.Resource["date"].ToString())); - Assert.AreEqual(patchDate.ToString(), response.Resource["dateStream"].ToString()); - Assert.AreNotEqual(response.Resource["date"], response.Resource["dateStream"]); - } - - [TestMethod] - public async Task ItemPatchStreamInputTest() - { - dynamic testItem = new - { - id = "test", - cost = (double?)null, - totalCost = 98.2789, - pk = "MyCustomStatus", - taskNum = 4909, - itemIds = new int[] { 1, 5, 10 }, - itemCode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, - }; - - // Create item - await this.Container.CreateItemAsync(item: testItem); - ContainerInternal containerInternal = (ContainerInternal)this.Container; - - dynamic testItemUpdated = new - { - cost = 100, - totalCost = 198.2789, - taskNum = 4910, - itemCode = new byte?[3] { 0x14, (byte)'\0', (byte)'{' } - }; - - CosmosJsonDotNetSerializer cosmosJsonDotNetSerializer = new CosmosJsonDotNetSerializer(); - - List patchOperations = new List() - { - PatchOperation.Replace("/cost", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.cost)), - PatchOperation.Replace("/totalCost", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.totalCost)), - PatchOperation.Replace("/taskNum", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.taskNum)), - PatchOperation.Replace("/itemCode", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.itemCode)), - }; - - ItemResponse response = await containerInternal.PatchItemAsync( - id: testItem.id, - partitionKey: new Cosmos.PartitionKey(testItem.pk), - patchOperations: patchOperations); - - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.IsNotNull(response.Resource); - - Assert.AreEqual(testItemUpdated.cost.ToString(), response.Resource.cost.ToString()); - Assert.AreEqual(testItemUpdated.totalCost.ToString(), response.Resource.totalCost.ToString()); - Assert.AreEqual(testItemUpdated.taskNum.ToString(), response.Resource.taskNum.ToString()); - Assert.AreEqual(testItemUpdated.itemCode[0].ToString(), response.Resource.itemCode[0].ToString()); - Assert.AreEqual(testItemUpdated.itemCode[1].ToString(), response.Resource.itemCode[1].ToString()); - Assert.AreEqual(testItemUpdated.itemCode[2].ToString(), response.Resource.itemCode[2].ToString()); - } - - // Read write non partition Container item. - [TestMethod] - public async Task ReadNonPartitionItemAsync() - { - ContainerInternal fixedContainer = null; - try - { - fixedContainer = await NonPartitionedContainerHelper.CreateNonPartitionedContainer( - this.database, - "ReadNonPartition" + Guid.NewGuid()); - - await NonPartitionedContainerHelper.CreateItemInNonPartitionedContainer(fixedContainer, nonPartitionItemId); - await NonPartitionedContainerHelper.CreateUndefinedPartitionItem((ContainerInlineCore)this.Container, undefinedPartitionItemId); - - ContainerResponse containerResponse = await fixedContainer.ReadContainerAsync(); - Assert.IsTrue(containerResponse.Resource.PartitionKey.Paths.Count > 0); - Assert.AreEqual(PartitionKey.SystemKeyPath, containerResponse.Resource.PartitionKey.Paths[0]); - - //Reading item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. - ItemResponse response = await fixedContainer.ReadItemAsync( - partitionKey: Cosmos.PartitionKey.None, - id: nonPartitionItemId); - - Assert.IsNotNull(response.Resource); - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - Assert.AreEqual(nonPartitionItemId, response.Resource.id); - - //Adding item to fixed container with CosmosContainerSettings.NonePartitionKeyValue. - ToDoActivity itemWithoutPK = ToDoActivity.CreateRandomToDoActivity(); - ItemResponse createResponseWithoutPk = await fixedContainer.CreateItemAsync( - item: itemWithoutPK, - partitionKey: Cosmos.PartitionKey.None); - - Assert.IsNotNull(createResponseWithoutPk.Resource); - Assert.AreEqual(HttpStatusCode.Created, createResponseWithoutPk.StatusCode); - Assert.AreEqual(itemWithoutPK.id, createResponseWithoutPk.Resource.id); - - //Updating item on fixed container with CosmosContainerSettings.NonePartitionKeyValue. - itemWithoutPK.pk = "updatedStatus"; - ItemResponse updateResponseWithoutPk = await fixedContainer.ReplaceItemAsync( - id: itemWithoutPK.id, - item: itemWithoutPK, - partitionKey: Cosmos.PartitionKey.None); - - Assert.IsNotNull(updateResponseWithoutPk.Resource); - Assert.AreEqual(HttpStatusCode.OK, updateResponseWithoutPk.StatusCode); - Assert.AreEqual(itemWithoutPK.id, updateResponseWithoutPk.Resource.id); - - //Adding item to fixed container with non-none PK. - ToDoActivityAfterMigration itemWithPK = this.CreateRandomToDoActivityAfterMigration("TestPk"); - ItemResponse createResponseWithPk = await fixedContainer.CreateItemAsync( - item: itemWithPK); - - Assert.IsNotNull(createResponseWithPk.Resource); - Assert.AreEqual(HttpStatusCode.Created, createResponseWithPk.StatusCode); - Assert.AreEqual(itemWithPK.id, createResponseWithPk.Resource.id); - - //Quering items on fixed container with cross partition enabled. - QueryDefinition sql = new QueryDefinition("select * from r"); - using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() { MaxConcurrency = 1, MaxItemCount = 10 })) - { - while (feedIterator.HasMoreResults) - { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(3, queryResponse.Count()); - } - } - - //Reading all items on fixed container. - using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator(requestOptions: new QueryRequestOptions() { MaxItemCount = 10 })) - { - while (feedIterator.HasMoreResults) - { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(3, queryResponse.Count()); - } - } - - //Quering items on fixed container with CosmosContainerSettings.NonePartitionKeyValue. - using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( - new QueryDefinition("select * from r"), - requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = Cosmos.PartitionKey.None, })) - { - while (feedIterator.HasMoreResults) - { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(2, queryResponse.Count()); - } - } - - //use ReadFeed on fixed container with CosmosContainerSettings.NonePartitionKeyValue. - using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( - queryText: null, - requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = Cosmos.PartitionKey.None, })) - { - while (feedIterator.HasMoreResults) - { - FeedResponse readFeedResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(2, readFeedResponse.Count()); - } - } - - //Quering items on fixed container with non-none PK. - using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = new Cosmos.PartitionKey(itemWithPK.partitionKey) })) - { - while (feedIterator.HasMoreResults) - { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(1, queryResponse.Count()); - } - } - - //ReadFeed on on fixed container with non-none PK. - using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( - queryText: null, - requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = new Cosmos.PartitionKey(itemWithPK.partitionKey) })) - { - while (feedIterator.HasMoreResults) - { - FeedResponse readFeedResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(1, readFeedResponse.Count()); - } - } - - //Deleting item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. - ItemResponse deleteResponseWithoutPk = await fixedContainer.DeleteItemAsync( - partitionKey: Cosmos.PartitionKey.None, - id: itemWithoutPK.id); - - Assert.IsNull(deleteResponseWithoutPk.Resource); - Assert.AreEqual(HttpStatusCode.NoContent, deleteResponseWithoutPk.StatusCode); - - //Deleting item from fixed container with non-none PK. - ItemResponse deleteResponseWithPk = await fixedContainer.DeleteItemAsync( - partitionKey: new Cosmos.PartitionKey(itemWithPK.partitionKey), - id: itemWithPK.id); - - Assert.IsNull(deleteResponseWithPk.Resource); - Assert.AreEqual(HttpStatusCode.NoContent, deleteResponseWithPk.StatusCode); - - //Reading item from partitioned container with CosmosContainerSettings.NonePartitionKeyValue. - ItemResponse undefinedItemResponse = await this.Container.ReadItemAsync( - partitionKey: Cosmos.PartitionKey.None, - id: undefinedPartitionItemId); - - Assert.IsNotNull(undefinedItemResponse.Resource); - Assert.AreEqual(HttpStatusCode.OK, undefinedItemResponse.StatusCode); - Assert.AreEqual(undefinedPartitionItemId, undefinedItemResponse.Resource.id); - } - finally - { - if (fixedContainer != null) - { - await fixedContainer.DeleteContainerStreamAsync(); - } - } - } - - // Move the data from None Partition to other logical partitions - [TestMethod] - public async Task MigrateDataInNonPartitionContainer() - { - ContainerInternal fixedContainer = null; - try - { - fixedContainer = await NonPartitionedContainerHelper.CreateNonPartitionedContainer( - this.database, - "ItemTestMigrateData" + Guid.NewGuid().ToString()); - - const int ItemsToCreate = 4; - // Insert a few items with no Partition Key - for (int i = 0; i < ItemsToCreate; i++) - { - await NonPartitionedContainerHelper.CreateItemInNonPartitionedContainer(fixedContainer, Guid.NewGuid().ToString()); - } - - // Read the container metadata - ContainerResponse containerResponse = await fixedContainer.ReadContainerAsync(); - - // Query items on the container that have no partition key value - int resultsFetched = 0; - QueryDefinition sql = new QueryDefinition("select * from r "); - FeedIterator setIterator = fixedContainer.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() { MaxItemCount = 2, PartitionKey = Cosmos.PartitionKey.None, }); - - while (setIterator.HasMoreResults) - { - FeedResponse queryResponse = await setIterator.ReadNextAsync(); - resultsFetched += queryResponse.Count(); - - // For the items returned with NonePartitionKeyValue - IEnumerator iter = queryResponse.GetEnumerator(); - while (iter.MoveNext()) - { - ToDoActivity activity = iter.Current; - - // Re-Insert into container with a partition key - ToDoActivityAfterMigration itemWithPK = new ToDoActivityAfterMigration - { id = activity.id, cost = activity.cost, description = activity.description, partitionKey = "TestPK", taskNum = activity.taskNum }; - ItemResponse createResponseWithPk = await fixedContainer.CreateItemAsync( - item: itemWithPK); - Assert.AreEqual(HttpStatusCode.Created, createResponseWithPk.StatusCode); - - // Deleting item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. - ItemResponse deleteResponseWithoutPk = await fixedContainer.DeleteItemAsync( - partitionKey: Cosmos.PartitionKey.None, - id: activity.id); - Assert.AreEqual(HttpStatusCode.NoContent, deleteResponseWithoutPk.StatusCode); - } - } - - // Validate all items with no partition key value are returned - Assert.AreEqual(ItemsToCreate, resultsFetched); - - // Re-Query the items on the container with NonePartitionKeyValue - setIterator = fixedContainer.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() { MaxItemCount = ItemsToCreate, PartitionKey = Cosmos.PartitionKey.None, }); - - Assert.IsTrue(setIterator.HasMoreResults); - { - FeedResponse queryResponse = await setIterator.ReadNextAsync(); - Assert.AreEqual(0, queryResponse.Count()); - } - - // Query the items with newly inserted PartitionKey - setIterator = fixedContainer.GetItemQueryIterator( - sql, - requestOptions: new QueryRequestOptions() { MaxItemCount = ItemsToCreate + 1, PartitionKey = new Cosmos.PartitionKey("TestPK"), }); - - Assert.IsTrue(setIterator.HasMoreResults); - { - FeedResponse queryResponse = await setIterator.ReadNextAsync(); - Assert.AreEqual(ItemsToCreate, queryResponse.Count()); - } - } - finally - { - if (fixedContainer != null) - { - await fixedContainer.DeleteContainerStreamAsync(); - } - } - } - - - [TestMethod] - [DataRow(false)] - [DataRow(true)] - [TestCategory("Quarantine") /* Gated runs emulator as rate limiting disabled */] - public async Task VerifyToManyRequestTest(bool isQuery) - { - using CosmosClient client = TestCommon.CreateCosmosClient(); - Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync("LoadTest"); - Container container = await db.CreateContainerIfNotExistsAsync("LoadContainer", "/pk"); - - try - { - Task[] createItems = new Task[300]; - for (int i = 0; i < createItems.Length; i++) - { - ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity(); - createItems[i] = container.CreateItemStreamAsync( - partitionKey: new Cosmos.PartitionKey(temp.pk), - streamPayload: TestCommon.SerializerCore.ToStream(temp)); - } - - Task.WaitAll(createItems); - - List createQuery = new List(500); - List failedToManyRequests = new List(); - for (int i = 0; i < 500 && failedToManyRequests.Count == 0; i++) - { - createQuery.Add(VerifyQueryToManyExceptionAsync( - container, - isQuery, - failedToManyRequests)); - } - - Task[] tasks = createQuery.ToArray(); - Task.WaitAll(tasks); - - Assert.IsTrue(failedToManyRequests.Count > 0, "Rate limiting appears to be disabled"); - ResponseMessage failedResponseMessage = failedToManyRequests.First(); - Assert.AreEqual(failedResponseMessage.StatusCode, (HttpStatusCode)429); - Assert.IsNotNull(failedResponseMessage.ErrorMessage); - string diagnostics = failedResponseMessage.Diagnostics.ToString(); - Assert.IsNotNull(diagnostics); - } - finally - { - await db.DeleteStreamAsync(); - } - } - - [TestMethod] - public async Task VerifySessionTokenPassThrough() - { - ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity("TBD"); - - ItemResponse responseAstype = await this.Container.CreateItemAsync(partitionKey: new Cosmos.PartitionKey(temp.pk), item: temp); - - string sessionToken = responseAstype.Headers.Session; - Assert.IsNotNull(sessionToken); - - ResponseMessage readResponse = await this.Container.ReadItemStreamAsync(temp.id, new Cosmos.PartitionKey(temp.pk), new ItemRequestOptions() { SessionToken = sessionToken }); - - Assert.AreEqual(HttpStatusCode.OK, readResponse.StatusCode); - Assert.IsNotNull(readResponse.Headers.Session); - Assert.AreEqual(sessionToken, readResponse.Headers.Session); - } - - [TestMethod] - public async Task VerifySessionNotFoundStatistics() - { - using CosmosClient cosmosClient = TestCommon.CreateCosmosClient(new CosmosClientOptions() { ConsistencyLevel = Cosmos.ConsistencyLevel.Session }); - DatabaseResponse database = await cosmosClient.CreateDatabaseIfNotExistsAsync("NoSession"); - Container container = await database.Database.CreateContainerIfNotExistsAsync("NoSession", "/pk"); - - try - { - ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity("TBD"); - - ItemResponse responseAstype = await container.CreateItemAsync(partitionKey: new Cosmos.PartitionKey(temp.pk), item: temp); - - string invalidSessionToken = this.GetDifferentLSNToken(responseAstype.Headers.Session, 2000); - - try - { - ItemResponse readResponse = await container.ReadItemAsync(temp.id, new Cosmos.PartitionKey(temp.pk), new ItemRequestOptions() { SessionToken = invalidSessionToken }); - Assert.AreEqual(HttpStatusCode.NotFound, readResponse.StatusCode); - Assert.Fail("Should had thrown ReadSessionNotAvailable"); - } - catch (CosmosException cosmosException) - { - Assert.IsFalse(cosmosException.Message.Contains("The read session is not available for the input session token."), cosmosException.Message); - string exception = cosmosException.ToString(); - Assert.IsTrue(exception.Contains("Point Operation Statistics"), exception); - } - } - finally - { - await database.Database.DeleteStreamAsync(); - } - } - - private string GetDifferentLSNToken(string token, long lsnDifferent) - { - string[] tokenParts = token.Split(':'); - ISessionToken sessionToken = SessionTokenHelper.Parse(tokenParts[1]); - ISessionToken differentSessionToken = TestCommon.CreateSessionToken(sessionToken, sessionToken.LSN + lsnDifferent); - return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", tokenParts[0], differentSessionToken.ConvertToString()); - } - - /// - /// Stateless container re-create test. - /// Create two client instances and do meta data operations through a single client - /// but do all validation using both clients. - /// - [DataRow(true, true)] - [DataRow(true, false)] - [DataRow(false, true)] - [DataRow(false, false)] - [DataTestMethod] - public async Task ContainterReCreateStatelessTest(bool operationBetweenRecreate, bool isQuery) - { - Func operation; - if (isQuery) - { - operation = ExecuteQueryAsync; - } - else - { - operation = ExecuteReadFeedAsync; - } - - using CosmosClient cc1 = TestCommon.CreateCosmosClient(); - using CosmosClient cc2 = TestCommon.CreateCosmosClient(); - Cosmos.Database db1 = null; - try - { - string dbName = Guid.NewGuid().ToString(); - string containerName = Guid.NewGuid().ToString(); - - db1 = await cc1.CreateDatabaseAsync(dbName); - ContainerInternal container1 = (ContainerInlineCore)await db1.CreateContainerAsync(containerName, "/id"); - - await operation(container1, HttpStatusCode.OK); - - // Read through client2 -> return 404 - Container container2 = cc2.GetDatabase(dbName).GetContainer(containerName); - await operation(container2, HttpStatusCode.OK); - - // Delete container - await container1.DeleteContainerAsync(); - - if (operationBetweenRecreate) - { - // Read on deleted container through client1 - await operation(container1, HttpStatusCode.NotFound); - - // Read on deleted container through client2 - await operation(container2, HttpStatusCode.NotFound); - } - - // Re-create again - container1 = (ContainerInlineCore)await db1.CreateContainerAsync(containerName, "/id"); - - // Read through client1 - await operation(container1, HttpStatusCode.OK); - - // Read through client2 - await operation(container2, HttpStatusCode.OK); - } - finally - { - await db1.DeleteStreamAsync(); - cc1.Dispose(); - cc2.Dispose(); - } - } - - [TestMethod] - public async Task NoAutoGenerateIdTest() - { - try - { - ToDoActivity t = new ToDoActivity - { - pk = "AutoID" - }; - ItemResponse responseAstype = await this.Container.CreateItemAsync( - partitionKey: new Cosmos.PartitionKey(t.pk), item: t); - - Assert.Fail("Unexpected ID auto-generation"); - } - catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.BadRequest) - { - } - } - - [TestMethod] - public async Task AutoGenerateIdPatternTest() - { - ToDoActivity itemWithoutId = new ToDoActivity - { - pk = "AutoID" - }; - - ToDoActivity createdItem = await this.AutoGenerateIdPatternTest( - new Cosmos.PartitionKey(itemWithoutId.pk), itemWithoutId); - - Assert.IsNotNull(createdItem.id); - Assert.AreEqual(itemWithoutId.pk, createdItem.pk); - } - - [TestMethod] - [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task CustomPropertiesItemRequestOptionsTest(bool binaryEncodingEnabledInClient) - { - try - { - if (binaryEncodingEnabledInClient) - { - Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, "True"); - } - - string customHeaderName = "custom-header1"; - string customHeaderValue = "value1"; - - CosmosClient clientWithIntercepter = TestCommon.CreateCosmosClient( - builder => builder.WithTransportClientHandlerFactory(transportClient => new TransportClientHelper.TransportClientWrapper( - transportClient, - (uri, resourceOperation, request) => - { - if (resourceOperation.resourceType == ResourceType.Document && - resourceOperation.operationType == OperationType.Create) - { - bool customHeaderExists = request.Properties.TryGetValue(customHeaderName, out object value); - - Assert.IsTrue(customHeaderExists); - Assert.AreEqual(customHeaderValue, value); - } - }))); - - Container container = clientWithIntercepter.GetContainer(this.database.Id, this.Container.Id); - - ToDoActivity temp = ToDoActivity.CreateRandomToDoActivity("TBD"); - - Dictionary properties = new Dictionary() - { - { customHeaderName, customHeaderValue}, - }; - - ItemRequestOptions ro = new ItemRequestOptions + ItemRequestOptions ro = new ItemRequestOptions { Properties = properties }; @@ -3616,17 +3615,17 @@ public async Task CustomPropertiesItemRequestOptionsTest(bool binaryEncodingEnab requestOptions: ro); Assert.AreEqual(HttpStatusCode.Created, responseAstype.StatusCode); - } + } finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - + } + } + [TestMethod] [DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")] - [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] - public async Task RegionsContactedTest(bool binaryEncodingEnabledInClient) + [DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")] + public async Task RegionsContactedTest(bool binaryEncodingEnabledInClient) { try { @@ -3646,224 +3645,224 @@ public async Task RegionsContactedTest(bool binaryEncodingEnabledInClient) finally { Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null); - } - } - - [TestMethod] - public async Task HaLayerDoesNotThrowNullOnGoneExceptionTest() - { - CosmosClientOptions clientOptions = new CosmosClientOptions() - { - TransportClientHandlerFactory = (x) => - new TransportClientWrapper(client: x, interceptor: (uri, resource, dsr) => - { - dsr.RequestContext.ClientRequestStatistics.GetType().GetField("systemUsageHistory", BindingFlags.NonPublic | BindingFlags.Instance).SetValue( - dsr.RequestContext.ClientRequestStatistics, - new Documents.Rntbd.SystemUsageHistory(new List() - { - new Documents.Rntbd.SystemUsageLoad( - DateTime.UtcNow, - Documents.Rntbd.ThreadInformation.Get(), - 80, - 9000), - new Documents.Rntbd.SystemUsageLoad( - DateTime.UtcNow - TimeSpan.FromSeconds(10), - Documents.Rntbd.ThreadInformation.Get(), - 95, - 9000) - }.AsReadOnly(), - TimeSpan.FromMinutes(1))); - if (resource.operationType.IsReadOperation()) - { - throw Documents.Rntbd.TransportExceptions.GetGoneException( - uri, - Guid.NewGuid()); - } - }) - }; - - CosmosClient cosmosClient = TestCommon.CreateCosmosClient(clientOptions); - Container container = cosmosClient.GetContainer(this.database.Id, this.Container.Id); - ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); - await container.CreateItemAsync(testItem); - - try - { - await container.ReadItemAsync(testItem.id, new Cosmos.PartitionKey(testItem.pk)); - } - catch (CosmosException ex) - { - Assert.AreEqual(ex.StatusCode, HttpStatusCode.ServiceUnavailable); - CosmosTraceDiagnostics diagnostics = (CosmosTraceDiagnostics)ex.Diagnostics; - Assert.IsTrue(diagnostics.IsGoneExceptionHit()); - string diagnosticString = diagnostics.ToString(); - Assert.IsFalse(string.IsNullOrEmpty(diagnosticString)); - Assert.IsTrue(diagnosticString.Contains("ForceAddressRefresh")); - Assert.IsTrue(diagnosticString.Contains("No change to cache")); - Assert.AreNotEqual(0, diagnostics.GetFailedRequestCount()); - } - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("ReadItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + - "ReadItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenReadItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.ReadItemStreamAsync( - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("ReadItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + - "ReadItemAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenReadItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.ReadItemAsync( - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("DeleteItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + - "DeleteItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenDeleteItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.DeleteItemStreamAsync( - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("DeleteItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + - "DeleteItemAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenDeleteItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.DeleteItemAsync( - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("DeleteItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + - "DeleteItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenReplaceItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.ReplaceItemStreamAsync( - streamPayload: new MemoryStream(), - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("ReplaceItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + - "ReplaceItemAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenReplaceItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.ReplaceItemAsync( - item: toDoActivity, - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("PatchItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + - "PatchItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenPatchItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - List patchOperations = new() - { - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Replace("/taskNum", 1) - }; - - await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.PatchItemStreamAsync( - patchOperations: patchOperations, - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); - } - - /// - /// - /// - /// - [TestMethod] - [Owner("philipthomas")] - [Description("PatchItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + - "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + - "PatchItemAsync should yield a CosmosException with a NotFound StatusCode.")] - public async Task GivenPatchItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() - { - List patchOperations = new() - { - PatchOperation.Add("/children/1/pk", "patched"), - PatchOperation.Remove("/description"), - PatchOperation.Replace("/taskNum", 1) - }; - - await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.PatchItemAsync( - patchOperations: patchOperations, - id: itemIdThatWillNotExist, - partitionKey: new Cosmos.PartitionKey(partitionKeyValue), - cancellationToken: cancellationToken)); + } + } + + [TestMethod] + public async Task HaLayerDoesNotThrowNullOnGoneExceptionTest() + { + CosmosClientOptions clientOptions = new CosmosClientOptions() + { + TransportClientHandlerFactory = (x) => + new TransportClientWrapper(client: x, interceptor: (uri, resource, dsr) => + { + dsr.RequestContext.ClientRequestStatistics.GetType().GetField("systemUsageHistory", BindingFlags.NonPublic | BindingFlags.Instance).SetValue( + dsr.RequestContext.ClientRequestStatistics, + new Documents.Rntbd.SystemUsageHistory(new List() + { + new Documents.Rntbd.SystemUsageLoad( + DateTime.UtcNow, + Documents.Rntbd.ThreadInformation.Get(), + 80, + 9000), + new Documents.Rntbd.SystemUsageLoad( + DateTime.UtcNow - TimeSpan.FromSeconds(10), + Documents.Rntbd.ThreadInformation.Get(), + 95, + 9000) + }.AsReadOnly(), + TimeSpan.FromMinutes(1))); + if (resource.operationType.IsReadOperation()) + { + throw Documents.Rntbd.TransportExceptions.GetGoneException( + uri, + Guid.NewGuid()); + } + }) + }; + + CosmosClient cosmosClient = TestCommon.CreateCosmosClient(clientOptions); + Container container = cosmosClient.GetContainer(this.database.Id, this.Container.Id); + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + await container.CreateItemAsync(testItem); + + try + { + await container.ReadItemAsync(testItem.id, new Cosmos.PartitionKey(testItem.pk)); + } + catch (CosmosException ex) + { + Assert.AreEqual(ex.StatusCode, HttpStatusCode.ServiceUnavailable); + CosmosTraceDiagnostics diagnostics = (CosmosTraceDiagnostics)ex.Diagnostics; + Assert.IsTrue(diagnostics.IsGoneExceptionHit()); + string diagnosticString = diagnostics.ToString(); + Assert.IsFalse(string.IsNullOrEmpty(diagnosticString)); + Assert.IsTrue(diagnosticString.Contains("ForceAddressRefresh")); + Assert.IsTrue(diagnosticString.Contains("No change to cache")); + Assert.AreNotEqual(0, diagnostics.GetFailedRequestCount()); + } + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("ReadItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + + "ReadItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenReadItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.ReadItemStreamAsync( + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("ReadItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + + "ReadItemAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenReadItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.ReadItemAsync( + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("DeleteItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + + "DeleteItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenDeleteItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.DeleteItemStreamAsync( + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("DeleteItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + + "DeleteItemAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenDeleteItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.DeleteItemAsync( + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("DeleteItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + + "DeleteItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenReplaceItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.ReplaceItemStreamAsync( + streamPayload: new MemoryStream(), + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("ReplaceItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + + "ReplaceItemAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenReplaceItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.ReplaceItemAsync( + item: toDoActivity, + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("PatchItemStreamAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and the MissingMemberHandling is set to MissingMemberHandling.Error. " + + "PatchItemStreamAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenPatchItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + List patchOperations = new() + { + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Replace("/taskNum", 1) + }; + + await CosmosItemTests.GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemStreamAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken) => await container.PatchItemStreamAsync( + patchOperations: patchOperations, + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); + } + + /// + /// + /// + /// + [TestMethod] + [Owner("philipthomas")] + [Description("PatchItemAsync is yielding a Newtonsoft.Json.JsonSerializationException whenever " + + "the item is not found and MissingMemberHandling is set to MissingMemberHandling.Error. " + + "PatchItemAsync should yield a CosmosException with a NotFound StatusCode.")] + public async Task GivenPatchItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync() + { + List patchOperations = new() + { + PatchOperation.Add("/children/1/pk", "patched"), + PatchOperation.Remove("/description"), + PatchOperation.Replace("/taskNum", 1) + }; + + await CosmosItemTests.GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + itemAsync: async (container, itemIdThatWillNotExist, partitionKeyValue, toDoActivity, cancellationToken) => await container.PatchItemAsync( + patchOperations: patchOperations, + id: itemIdThatWillNotExist, + partitionKey: new Cosmos.PartitionKey(partitionKeyValue), + cancellationToken: cancellationToken)); } [TestMethod] @@ -4193,297 +4192,297 @@ public async Task QueryItemAsyncTest(bool binaryEncodingEnabled) throughput: 4000); return (client, db, container); - } - - private static async Task GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - Func> itemStreamAsync) - { - // AAA - // Arrange - CancellationTokenSource cancellationTokenSource = new(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - // Food for thought, actionable items. - // - // 1. Is there anything else that we should be concerned with that would give us the same behavior? - // 2. Are there other operations other than those that can yield an NotFound exception that we should be - // concerned with? - // 3. Can we also reset the DefaultSettings before we make the call, and reset it back once it is done? - - JsonConvert.DefaultSettings = () => new JsonSerializerSettings - { - MissingMemberHandling = MissingMemberHandling.Error - }; - - CosmosClient cosmosClient = TestCommon.CreateCosmosClient(); - - string databaseId = Guid.NewGuid().ToString(); - Cosmos.Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync( - id: databaseId, - cancellationToken: cancellationToken); - - try - { - string containerId = Guid.NewGuid().ToString(); - Container container = await database.CreateContainerIfNotExistsAsync( - containerProperties: new ContainerProperties - { - Id = containerId, - PartitionKeyPath = "/pk", - }, - cancellationToken: cancellationToken); - - - // Act - string itemIdThatWillNotExist = Guid.NewGuid().ToString(); - string partitionKeyValue = Guid.NewGuid().ToString(); - - ResponseMessage response = await itemStreamAsync(container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken); - - // Assert - Debug.Assert( - condition: response != null, - message: $"{response}"); - - Assert.AreEqual( - expected: HttpStatusCode.NotFound, - actual: response.StatusCode); - - string content = JsonConvert.SerializeObject(response.Content); - - Assert.AreEqual( - expected: "null", - actual: content); - - string errorMessage = JsonConvert.SerializeObject(response.ErrorMessage); - - Assert.IsNotNull(value: errorMessage); - - Debug.Assert( - condition: response.CosmosException != null, - message: $"{response.CosmosException}"); - - Assert.AreEqual( - expected: HttpStatusCode.NotFound, - actual: response.StatusCode); - - Debug.WriteLine(message: $"{nameof(response.CosmosException)}: {response.CosmosException}"); - - Assert.AreEqual( - actual: response.CosmosException.StatusCode, - expected: HttpStatusCode.NotFound); - } - finally - { - if (database != null) - { - // Remove the test database. Cleanup. - _ = await database.DeleteAsync(cancellationToken: cancellationToken); - - Debug.WriteLine($"The {nameof(database)} with id '{databaseId}' was removed."); - } - - // Setting this back because it blows up other serialization tests. - - JsonConvert.DefaultSettings = () => default; - } - } - - private static async Task GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( - Func>> itemAsync) - { - // AAA - // Arrange - CancellationTokenSource cancellationTokenSource = new(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - - // Food for thought, actionable items. - // - // 1. Is there anything else that we should be concerned with that would give us the same behavior? - // 2. Are there other operations other than those that can yield an NotFound exception that we should be - // concerned with? - // 3. Can we also reset the DefaultSettings before we make the call, and reset it back once it is done? - - JsonConvert.DefaultSettings = () => new JsonSerializerSettings - { - MissingMemberHandling = MissingMemberHandling.Error - }; - - CosmosClient cosmosClient = TestCommon.CreateCosmosClient(); - - string databaseId = Guid.NewGuid().ToString(); - Cosmos.Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync( - id: databaseId, - cancellationToken: cancellationToken); - - try - { - string containerId = Guid.NewGuid().ToString(); - Container container = await database.CreateContainerIfNotExistsAsync( - containerProperties: new ContainerProperties - { - Id = containerId, - PartitionKeyPath = "/pk", - }, - cancellationToken: cancellationToken); - - - // Act - // If any thing other than a CosmosException is thrown, the call to ReadItemAsync below will fail. - string itemIdThatWillNotExist = Guid.NewGuid().ToString(); - string partitionKeyValue = Guid.NewGuid().ToString(); - - CosmosException cosmosException = await Assert.ThrowsExceptionAsync(action: - async () => await itemAsync(container, itemIdThatWillNotExist, partitionKeyValue, new ToDoActivity { id = Guid.NewGuid().ToString(), pk = "Georgia" }, cancellationToken)) ; - - // Assert - Debug.Assert( - condition: cosmosException != null, - message: $"{cosmosException}"); - - Debug.WriteLine(message: $"{nameof(cosmosException)}: {cosmosException}"); - - Assert.AreEqual( - actual: cosmosException.StatusCode, - expected: HttpStatusCode.NotFound); - } - finally - { - if (database != null) - { - // Remove the test database. Cleanup. - _ = await database.DeleteAsync(cancellationToken: cancellationToken); - - Debug.WriteLine($"The {nameof(database)} with id '{databaseId}' was removed."); - } - - // Setting this back because it blows up other serialization tests. - - JsonConvert.DefaultSettings = () => default; - } - } - - private async Task AutoGenerateIdPatternTest(Cosmos.PartitionKey pk, T itemWithoutId) - { - string autoId = Guid.NewGuid().ToString(); - - JObject tmpJObject = JObject.FromObject(itemWithoutId); - tmpJObject["id"] = autoId; - - ItemResponse response = await this.Container.CreateItemAsync( - partitionKey: pk, item: tmpJObject); - - return response.Resource.ToObject(); - } - - private static async Task VerifyQueryToManyExceptionAsync( - Container container, - bool isQuery, - List failedToManyMessages) - { - string queryText = null; - if (isQuery) - { - queryText = "select * from r"; - } - - FeedIterator iterator = container.GetItemQueryStreamIterator(queryText); - while (iterator.HasMoreResults && failedToManyMessages.Count == 0) - { - ResponseMessage response = await iterator.ReadNextAsync(); - if (response.StatusCode == (HttpStatusCode)429) - { - failedToManyMessages.Add(response); - return; - } - } - } - - private static void ValidateCosmosException(CosmosException exception) - { - if (exception.StatusCode == HttpStatusCode.RequestTimeout || - exception.StatusCode == HttpStatusCode.InternalServerError || - exception.StatusCode == HttpStatusCode.ServiceUnavailable) - { - Assert.IsTrue(exception.Message.Contains("Diagnostics")); - } - else - { - Assert.IsFalse(exception.Message.Contains("Diagnostics")); - } - - string toString = exception.ToString(); - Assert.AreEqual(1, Regex.Matches(toString, "Client Configuration").Count, $"The Cosmos Diagnostics does not exists or multiple instance are in the ToString(). {toString}"); - } - - private static async Task ExecuteQueryAsync(Container container, HttpStatusCode expected) - { - FeedIterator iterator = container.GetItemQueryStreamIterator("select * from r"); - while (iterator.HasMoreResults) - { - ResponseMessage response = await iterator.ReadNextAsync(); - Assert.AreEqual(expected, response.StatusCode, $"ExecuteQueryAsync substatuscode: {response.Headers.SubStatusCode} "); - } - } - - private static async Task ExecuteReadFeedAsync(Container container, HttpStatusCode expected) - { - FeedIterator iterator = container.GetItemQueryStreamIterator(); - while (iterator.HasMoreResults) - { - ResponseMessage response = await iterator.ReadNextAsync(); - Assert.AreEqual(expected, response.StatusCode, $"ExecuteReadFeedAsync substatuscode: {response.Headers.SubStatusCode} "); - } - } - - public class ToDoActivityAfterMigration - { - public string id { get; set; } - public int taskNum { get; set; } - public double cost { get; set; } - public string description { get; set; } - [JsonProperty(PropertyName = "_partitionKey")] - public string partitionKey { get; set; } - } - - private ToDoActivityAfterMigration CreateRandomToDoActivityAfterMigration(string pk = null, string id = null) - { - if (string.IsNullOrEmpty(pk)) - { - pk = "TBD" + Guid.NewGuid().ToString(); - } - if (id == null) - { - id = Guid.NewGuid().ToString(); - } - return new ToDoActivityAfterMigration() - { - id = id, - description = "CreateRandomToDoActivity", - partitionKey = pk, - taskNum = 42, - cost = double.MaxValue - }; - } - - private static async Task TestNonePKForNonExistingContainer(Container container) - { - // Stream implementation should not throw - ResponseMessage response = await container.ReadItemStreamAsync("id1", Cosmos.PartitionKey.None); - Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); - Assert.IsNotNull(response.Headers.ActivityId); - Assert.IsNotNull(response.ErrorMessage); - - // For typed, it will throw - try - { - ItemResponse typedResponse = await container.ReadItemAsync("id1", Cosmos.PartitionKey.None); - Assert.Fail("Should throw exception."); - } - catch (CosmosException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode); - } + } + + private static async Task GivenItemStreamAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + Func> itemStreamAsync) + { + // AAA + // Arrange + CancellationTokenSource cancellationTokenSource = new(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + // Food for thought, actionable items. + // + // 1. Is there anything else that we should be concerned with that would give us the same behavior? + // 2. Are there other operations other than those that can yield an NotFound exception that we should be + // concerned with? + // 3. Can we also reset the DefaultSettings before we make the call, and reset it back once it is done? + + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + MissingMemberHandling = MissingMemberHandling.Error + }; + + CosmosClient cosmosClient = TestCommon.CreateCosmosClient(); + + string databaseId = Guid.NewGuid().ToString(); + Cosmos.Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync( + id: databaseId, + cancellationToken: cancellationToken); + + try + { + string containerId = Guid.NewGuid().ToString(); + Container container = await database.CreateContainerIfNotExistsAsync( + containerProperties: new ContainerProperties + { + Id = containerId, + PartitionKeyPath = "/pk", + }, + cancellationToken: cancellationToken); + + + // Act + string itemIdThatWillNotExist = Guid.NewGuid().ToString(); + string partitionKeyValue = Guid.NewGuid().ToString(); + + ResponseMessage response = await itemStreamAsync(container, itemIdThatWillNotExist, partitionKeyValue, cancellationToken); + + // Assert + Debug.Assert( + condition: response != null, + message: $"{response}"); + + Assert.AreEqual( + expected: HttpStatusCode.NotFound, + actual: response.StatusCode); + + string content = JsonConvert.SerializeObject(response.Content); + + Assert.AreEqual( + expected: "null", + actual: content); + + string errorMessage = JsonConvert.SerializeObject(response.ErrorMessage); + + Assert.IsNotNull(value: errorMessage); + + Debug.Assert( + condition: response.CosmosException != null, + message: $"{response.CosmosException}"); + + Assert.AreEqual( + expected: HttpStatusCode.NotFound, + actual: response.StatusCode); + + Debug.WriteLine(message: $"{nameof(response.CosmosException)}: {response.CosmosException}"); + + Assert.AreEqual( + actual: response.CosmosException.StatusCode, + expected: HttpStatusCode.NotFound); + } + finally + { + if (database != null) + { + // Remove the test database. Cleanup. + _ = await database.DeleteAsync(cancellationToken: cancellationToken); + + Debug.WriteLine($"The {nameof(database)} with id '{databaseId}' was removed."); + } + + // Setting this back because it blows up other serialization tests. + + JsonConvert.DefaultSettings = () => default; + } + } + + private static async Task GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpectsCosmosExceptionTestAsync( + Func>> itemAsync) + { + // AAA + // Arrange + CancellationTokenSource cancellationTokenSource = new(); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + // Food for thought, actionable items. + // + // 1. Is there anything else that we should be concerned with that would give us the same behavior? + // 2. Are there other operations other than those that can yield an NotFound exception that we should be + // concerned with? + // 3. Can we also reset the DefaultSettings before we make the call, and reset it back once it is done? + + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + MissingMemberHandling = MissingMemberHandling.Error + }; + + CosmosClient cosmosClient = TestCommon.CreateCosmosClient(); + + string databaseId = Guid.NewGuid().ToString(); + Cosmos.Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync( + id: databaseId, + cancellationToken: cancellationToken); + + try + { + string containerId = Guid.NewGuid().ToString(); + Container container = await database.CreateContainerIfNotExistsAsync( + containerProperties: new ContainerProperties + { + Id = containerId, + PartitionKeyPath = "/pk", + }, + cancellationToken: cancellationToken); + + + // Act + // If any thing other than a CosmosException is thrown, the call to ReadItemAsync below will fail. + string itemIdThatWillNotExist = Guid.NewGuid().ToString(); + string partitionKeyValue = Guid.NewGuid().ToString(); + + CosmosException cosmosException = await Assert.ThrowsExceptionAsync(action: + async () => await itemAsync(container, itemIdThatWillNotExist, partitionKeyValue, new ToDoActivity { id = Guid.NewGuid().ToString(), pk = "Georgia" }, cancellationToken)) ; + + // Assert + Debug.Assert( + condition: cosmosException != null, + message: $"{cosmosException}"); + + Debug.WriteLine(message: $"{nameof(cosmosException)}: {cosmosException}"); + + Assert.AreEqual( + actual: cosmosException.StatusCode, + expected: HttpStatusCode.NotFound); + } + finally + { + if (database != null) + { + // Remove the test database. Cleanup. + _ = await database.DeleteAsync(cancellationToken: cancellationToken); + + Debug.WriteLine($"The {nameof(database)} with id '{databaseId}' was removed."); + } + + // Setting this back because it blows up other serialization tests. + + JsonConvert.DefaultSettings = () => default; + } + } + + private async Task AutoGenerateIdPatternTest(Cosmos.PartitionKey pk, T itemWithoutId) + { + string autoId = Guid.NewGuid().ToString(); + + JObject tmpJObject = JObject.FromObject(itemWithoutId); + tmpJObject["id"] = autoId; + + ItemResponse response = await this.Container.CreateItemAsync( + partitionKey: pk, item: tmpJObject); + + return response.Resource.ToObject(); + } + + private static async Task VerifyQueryToManyExceptionAsync( + Container container, + bool isQuery, + List failedToManyMessages) + { + string queryText = null; + if (isQuery) + { + queryText = "select * from r"; + } + + FeedIterator iterator = container.GetItemQueryStreamIterator(queryText); + while (iterator.HasMoreResults && failedToManyMessages.Count == 0) + { + ResponseMessage response = await iterator.ReadNextAsync(); + if (response.StatusCode == (HttpStatusCode)429) + { + failedToManyMessages.Add(response); + return; + } + } + } + + private static void ValidateCosmosException(CosmosException exception) + { + if (exception.StatusCode == HttpStatusCode.RequestTimeout || + exception.StatusCode == HttpStatusCode.InternalServerError || + exception.StatusCode == HttpStatusCode.ServiceUnavailable) + { + Assert.IsTrue(exception.Message.Contains("Diagnostics")); + } + else + { + Assert.IsFalse(exception.Message.Contains("Diagnostics")); + } + + string toString = exception.ToString(); + Assert.AreEqual(1, Regex.Matches(toString, "Client Configuration").Count, $"The Cosmos Diagnostics does not exists or multiple instance are in the ToString(). {toString}"); + } + + private static async Task ExecuteQueryAsync(Container container, HttpStatusCode expected) + { + FeedIterator iterator = container.GetItemQueryStreamIterator("select * from r"); + while (iterator.HasMoreResults) + { + ResponseMessage response = await iterator.ReadNextAsync(); + Assert.AreEqual(expected, response.StatusCode, $"ExecuteQueryAsync substatuscode: {response.Headers.SubStatusCode} "); + } + } + + private static async Task ExecuteReadFeedAsync(Container container, HttpStatusCode expected) + { + FeedIterator iterator = container.GetItemQueryStreamIterator(); + while (iterator.HasMoreResults) + { + ResponseMessage response = await iterator.ReadNextAsync(); + Assert.AreEqual(expected, response.StatusCode, $"ExecuteReadFeedAsync substatuscode: {response.Headers.SubStatusCode} "); + } + } + + public class ToDoActivityAfterMigration + { + public string id { get; set; } + public int taskNum { get; set; } + public double cost { get; set; } + public string description { get; set; } + [JsonProperty(PropertyName = "_partitionKey")] + public string partitionKey { get; set; } + } + + private ToDoActivityAfterMigration CreateRandomToDoActivityAfterMigration(string pk = null, string id = null) + { + if (string.IsNullOrEmpty(pk)) + { + pk = "TBD" + Guid.NewGuid().ToString(); + } + if (id == null) + { + id = Guid.NewGuid().ToString(); + } + return new ToDoActivityAfterMigration() + { + id = id, + description = "CreateRandomToDoActivity", + partitionKey = pk, + taskNum = 42, + cost = double.MaxValue + }; + } + + private static async Task TestNonePKForNonExistingContainer(Container container) + { + // Stream implementation should not throw + ResponseMessage response = await container.ReadItemStreamAsync("id1", Cosmos.PartitionKey.None); + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); + Assert.IsNotNull(response.Headers.ActivityId); + Assert.IsNotNull(response.ErrorMessage); + + // For typed, it will throw + try + { + ItemResponse typedResponse = await container.ReadItemAsync("id1", Cosmos.PartitionKey.None); + Assert.Fail("Should throw exception."); + } + catch (CosmosException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode); + } } private static void AssertOnResponseSerializationBinaryType( @@ -4528,6 +4527,6 @@ private static bool IsTextFormat( JsonSerializationFormat desiredFormat) { return desiredFormat == JsonSerializationFormat.Text && firstByte < (int)JsonSerializationFormat.Binary; - } - } -} + } + } +}