diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs index ba7a1d473c..6fa0b1606f 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchResponse.cs @@ -361,6 +361,7 @@ private static async Task PopulateFromContentAsync( HttpStatusCode responseStatusCode = responseMessage.StatusCode; SubStatusCodes responseSubStatusCode = responseMessage.Headers.SubStatusCode; + string responseErrorMessage = responseMessage.ErrorMessage; // Promote the operation error status as the Batch response error status if we have a MultiStatus response // to provide users with status codes they are used to. @@ -373,6 +374,16 @@ private static async Task PopulateFromContentAsync( { responseStatusCode = result.StatusCode; responseSubStatusCode = result.SubStatusCode; + + if (result.ResourceStream != null) + { + using (StreamReader reader = new StreamReader(result.ResourceStream, encoding: System.Text.Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) + { + responseErrorMessage = reader.ReadToEnd(); + result.ResourceStream.Position = 0; + } + } + break; } } @@ -381,7 +392,7 @@ private static async Task PopulateFromContentAsync( TransactionalBatchResponse response = new TransactionalBatchResponse( responseStatusCode, responseSubStatusCode, - responseMessage.ErrorMessage, + responseErrorMessage, responseMessage.Headers, trace, serverRequest.Operations, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchSchemaTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchSchemaTests.cs index f419974da9..6806756948 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchSchemaTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchSchemaTests.cs @@ -177,7 +177,59 @@ public async Task BatchResponseDeserializationAsync() Assert.IsTrue(comparer.Equals(results[1], batchResponse[1])); } - private class ItemBatchOperationEqualityComparer : IEqualityComparer + [TestMethod] + [Owner("nalutripician")] + public async Task BatchResponseDeserializationPromotesErrorMessageAsync() + { + string expectedErrorMessage = "{\"Errors\":[\"Resource with specified id or name already exists.\"]}"; + byte[] errorBody = System.Text.Encoding.UTF8.GetBytes(expectedErrorMessage); + + using CosmosClient cosmosClient = MockCosmosUtil.CreateMockCosmosClient(); + ContainerInternal containerCore = (ContainerInlineCore)cosmosClient.GetDatabase("db").GetContainer("cont"); + List results = new List + { + new TransactionalBatchOperationResult(HttpStatusCode.Conflict) + { + ResourceStream = new CloneableStream( + internalStream: new MemoryStream(errorBody, index: 0, count: errorBody.Length, writable: false, publiclyVisible: true), + allowUnsafeDataAccess: true), + }, + new TransactionalBatchOperationResult(HttpStatusCode.FailedDependency) + }; + + MemoryStream responseContent = await new BatchResponsePayloadWriter(results).GeneratePayloadAsync(); + + SinglePartitionKeyServerBatchRequest batchRequest = await SinglePartitionKeyServerBatchRequest.CreateAsync( + partitionKey: Cosmos.PartitionKey.None, + operations: new ArraySegment( + new ItemBatchOperation[] + { + new ItemBatchOperation(OperationType.Create, operationIndex: 0, id: "someId", containerCore: containerCore), + new ItemBatchOperation(OperationType.Create, operationIndex: 1, id: "someId2", containerCore: containerCore) + }), + serializerCore: MockCosmosUtil.Serializer, + trace: NoOpTrace.Singleton, + cancellationToken: CancellationToken.None); + + ResponseMessage response = new ResponseMessage((HttpStatusCode)StatusCodes.MultiStatus) { Content = responseContent }; + response.Headers.Session = Guid.NewGuid().ToString(); + response.Headers.ActivityId = Guid.NewGuid().ToString(); + + TransactionalBatchResponse batchResponse = await TransactionalBatchResponse.FromResponseMessageAsync( + response, + batchRequest, + MockCosmosUtil.Serializer, + true, + NoOpTrace.Singleton, + CancellationToken.None); + + Assert.IsNotNull(batchResponse); + Assert.AreEqual(HttpStatusCode.Conflict, batchResponse.StatusCode); + Assert.IsNotNull(batchResponse.ErrorMessage, "ErrorMessage should be promoted from the failing operation's ResourceStream"); + Assert.AreEqual(expectedErrorMessage, batchResponse.ErrorMessage); + } + + private class ItemBatchOperationEqualityComparer: IEqualityComparer { public bool Equals(ItemBatchOperation x, ItemBatchOperation y) {