diff --git a/extensions/Worker.Extensions.CosmosDB/release_notes.md b/extensions/Worker.Extensions.CosmosDB/release_notes.md index f9b91fced..06e3c8aae 100644 --- a/extensions/Worker.Extensions.CosmosDB/release_notes.md +++ b/extensions/Worker.Extensions.CosmosDB/release_notes.md @@ -7,3 +7,4 @@ ### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB 4.12.0 - Updated `Microsoft.Azure.WebJobs.Extensions.CosmosDB` reference to 4.9.0 +- Return a successful result with a null value when a Cosmos document cannot be found (#2942/#2545) diff --git a/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs b/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs index aeeb881d0..cc7274201 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs @@ -58,6 +58,10 @@ private async ValueTask ConvertFromBindingDataAsync(ConverterC return ConversionResult.Success(result); } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return ConversionResult.Success(null); + } catch (Exception ex) { return ConversionResult.Failed(ex); diff --git a/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs b/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs index 50038ed60..980a5f097 100644 --- a/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs +++ b/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs @@ -127,6 +127,11 @@ public async Task DocByIdFromRouteData( Id = "{id}", PartitionKey = "{partitionKey}")] MyDocument doc) { + if (doc == null) + { + return req.CreateResponse(HttpStatusCode.NotFound); + } + var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteStringAsync(doc.Text); return response; diff --git a/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs b/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs index 84167b864..55e3e9482 100644 --- a/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs +++ b/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs @@ -105,6 +105,28 @@ public async Task CosmosInput_DocByIdFromRouteData_Succeeds() } } + [Fact] + public async Task CosmosInput_DocByIdFromRouteDataNotFound_Succeeds() + { + string expectedDocId = Guid.NewGuid().ToString(); + string functionPath = $"docsbyroute/{expectedDocId}/{expectedDocId}"; + try + { + //Trigger + HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionPath); + + //Verify + HttpStatusCode expectedStatusCode = HttpStatusCode.NotFound; + + Assert.Equal(expectedStatusCode, response.StatusCode); + } + finally + { + //Clean up + await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); + } + } + [Fact] public async Task CosmosInput_DocByIdFromRouteDataUsingSqlQuery_Succeeds() { diff --git a/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs b/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs index 42e11050b..593ad8ef9 100644 --- a/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs +++ b/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -359,14 +360,18 @@ public async Task ConvertAsync_CosmosContainerIsNull_ThrowsException_ReturnsFail Assert.Equal($"Unable to create Cosmos container client for 'myContainer'.", conversionResult.Error.Message); } - [Fact] - public async Task ConvertAsync_POCO_IdProvided_StatusNot200_ThrowsException_ReturnsFailure() + [Theory] + [InlineData(HttpStatusCode.Conflict)] + [InlineData(HttpStatusCode.Forbidden)] + [InlineData(HttpStatusCode.InternalServerError)] + [InlineData(HttpStatusCode.BadRequest)] + public async Task ConvertAsync_POCO_IdProvided_NotSuccessStatus_ThrowsException_ReturnsFailure(HttpStatusCode httpStatusCode) { object grpcModelBindingData = GrpcTestHelper.GetTestGrpcModelBindingData(GetTestBinaryData(id: "1", partitionKey: "1"), "CosmosDB"); var context = new TestConverterContext(typeof(ToDoItem), grpcModelBindingData); var mockResponse = new Mock(); - var cosmosException = new CosmosException("test failure", System.Net.HttpStatusCode.NotFound, 0, "test", 0); + var cosmosException = new CosmosException("test failure", httpStatusCode, 0, "test", 0); mockResponse.Setup(x => x.EnsureSuccessStatusCode()).Throws(cosmosException); var mockContainer = new Mock(); @@ -384,6 +389,32 @@ public async Task ConvertAsync_POCO_IdProvided_StatusNot200_ThrowsException_Retu Assert.Equal("test failure", conversionResult.Error.Message); } + [Fact] + public async Task ConvertAsync_POCO_IdProvided_Status404_ReturnsSuccess() + { + object grpcModelBindingData = GrpcTestHelper.GetTestGrpcModelBindingData(GetTestBinaryData(id: "1", partitionKey: "1"), "CosmosDB"); + var context = new TestConverterContext(typeof(ToDoItem), grpcModelBindingData); + + var mockResponse = new Mock(); + mockResponse.Setup(x => x.IsSuccessStatusCode).Returns(false); + var cosmosException = new CosmosException("test failure", HttpStatusCode.NotFound, 0, "test", 0); + mockResponse.Setup(x => x.EnsureSuccessStatusCode()).Throws(cosmosException); + + var mockContainer = new Mock(); + mockContainer + .Setup(m => m.ReadItemStreamAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(mockResponse.Object); + + _mockCosmosClient + .Setup(m => m.GetContainer(It.IsAny(), It.IsAny())) + .Returns(mockContainer.Object); + + var conversionResult = await _cosmosDBConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + Assert.Null(conversionResult.Value); + } + private BinaryData GetTestBinaryData(string db = "testDb", string container = "testContainer", string connection = "cosmosConnection", string id = "", string partitionKey = "", string query = "", string location = "", string queryParams = "{}") { string jsonData = $@"{{