diff --git a/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs b/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs index efd8a0b84..76615a115 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs @@ -155,11 +155,12 @@ private async Task CreatePOCOCollectionAsync(Container container, Cos } } - PartitionKey partitionKey = String.IsNullOrEmpty(cosmosAttribute.PartitionKey) - ? PartitionKey.None - : new PartitionKey(cosmosAttribute.PartitionKey); - - QueryRequestOptions queryRequestOptions = new() { PartitionKey = partitionKey }; + QueryRequestOptions queryRequestOptions = new(); + if (!String.IsNullOrEmpty(cosmosAttribute.PartitionKey)) + { + var partitionKey = new PartitionKey(cosmosAttribute.PartitionKey); + queryRequestOptions = new() { PartitionKey = partitionKey }; + } using (var iterator = container.GetItemQueryIterator(queryDefinition: queryDefinition, requestOptions: queryRequestOptions)) { diff --git a/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs b/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs index 888636bd4..22bcd3521 100644 --- a/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs +++ b/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs @@ -3,20 +3,30 @@ using System.Collections.Generic; using System.Linq; -using System.Text.Json.Serialization; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.Functions.Worker.E2EApp { - public static class CosmosFunction + public class CosmosFunction { + private readonly ILogger _logger; + + public CosmosFunction(ILogger logger) + { + _logger = logger; + } + [Function(nameof(CosmosTrigger))] [CosmosDBOutput( databaseName: "%CosmosDb%", containerName: "%CosmosCollOut%", Connection = "CosmosConnection", CreateIfNotExists = true)] - public static object CosmosTrigger([CosmosDBTrigger( + public object CosmosTrigger([CosmosDBTrigger( databaseName: "%CosmosDb%", containerName: "%CosmosCollIn%", Connection = "CosmosConnection", @@ -27,24 +37,136 @@ public static object CosmosTrigger([CosmosDBTrigger( { foreach (var doc in input) { - context.GetLogger("Function.CosmosTrigger").LogInformation($"id: {doc.Id}"); + context.GetLogger("Function.CosmosTrigger").LogInformation($"id: {doc.Text}"); } - return input.Select(p => new { id = p.Id }); + return input.Select(p => new { id = p.Text }); } return null; } - public class MyDocument + [Function(nameof(DocsByUsingCosmosClient))] + public async Task DocsByUsingCosmosClient( + [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, + [CosmosDBInput("", "", Connection = "CosmosConnection")] CosmosClient client) { - public string Id { get; set; } + var container = client.GetContainer("ItemDb", "ItemCollectionIn"); + var iterator = container.GetItemQueryIterator("SELECT * FROM c"); - public string Text { get; set; } + var output = ""; + + while (iterator.HasMoreResults) + { + var documents = await iterator.ReadNextAsync(); + foreach (dynamic d in documents) + { + output += $"{(string)d.Text}, "; + } + } + + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteStringAsync(output); + return response; + } + + [Function(nameof(DocsByUsingDatabaseClient))] + public async Task DocsByUsingDatabaseClient( + [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, + [CosmosDBInput("%CosmosDb%", "", Connection = "CosmosConnection")] Database database) + { + var container = database.GetContainer("ItemCollectionIn");; + var iterator = container.GetItemQueryIterator("SELECT * FROM c"); + + var output = ""; + + while (iterator.HasMoreResults) + { + var documents = await iterator.ReadNextAsync(); + foreach (dynamic d in documents) + { + output += $"{(string)d.Text}, "; + } + } + + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteStringAsync(output); + return response; + } + + [Function(nameof(DocsByUsingContainerClient))] + public async Task DocsByUsingContainerClient( + [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, + [CosmosDBInput("%CosmosDb%", "%CosmosCollIn%", Connection = "CosmosConnection")] Container container) + { + var iterator = container.GetItemQueryIterator("SELECT * FROM c"); + + var output = ""; + + while (iterator.HasMoreResults) + { + var documents = await iterator.ReadNextAsync(); + foreach (dynamic d in documents) + { + output += $"{(string)d.Text}, "; + } + } + + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteStringAsync(output); + return response; + } - public int Number { get; set; } + [Function(nameof(DocByIdFromRouteData))] + public async Task DocByIdFromRouteData( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "docsbyroute/{partitionKey}/{id}")] HttpRequestData req, + [CosmosDBInput( + databaseName: "%CosmosDb%", + containerName: "%CosmosCollIn%", + Connection = "CosmosConnection", + Id = "{id}", + PartitionKey = "{partitionKey}")] MyDocument doc) + { + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteStringAsync(doc.Text); + return response; + } + + [Function(nameof(DocByIdFromRouteDataUsingSqlQuery))] + public async Task DocByIdFromRouteDataUsingSqlQuery( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "docsbysql/{id}")] HttpRequestData req, + [CosmosDBInput( + databaseName: "%CosmosDb%", + containerName: "%CosmosCollIn%", + Connection = "CosmosConnection", + SqlQuery = "SELECT * FROM ItemDb t where t.id = {id}")] + IEnumerable myDocs) + { + var output = myDocs.FirstOrDefault().Text; + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteStringAsync(output); + return response; + } - public bool Boolean { get; set; } + [Function(nameof(DocByIdFromQueryStringUsingSqlQuery))] + public async Task DocByIdFromQueryStringUsingSqlQuery( + [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, + [CosmosDBInput( + databaseName: "%CosmosDb%", + containerName: "%CosmosCollIn%", + Connection = "CosmosConnection", + SqlQuery = "SELECT * FROM ItemDb t where t.id = {id}")] + IEnumerable myDocs) + { + var output = myDocs.FirstOrDefault().Text; + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteStringAsync(output); + return response; + } + + public class MyDocument + { + public string Text { get; set; } } } } diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index 218be1a05..dab64e259 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -3,12 +3,12 @@ net6.0 v4 - + net7.0 v4 - + Exe <_FunctionsSkipCleanOutput>true @@ -46,7 +46,6 @@ - diff --git a/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs b/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs index 71fb75934..5e1593f3c 100644 --- a/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs +++ b/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -27,7 +29,7 @@ public async Task CosmosDBTriggerAndOutput_Succeeds() try { //Trigger - await CosmosDBHelpers.CreateDocument(expectedDocId); + await CosmosDBHelpers.CreateDocument(expectedDocId, expectedDocId); //Read var documentId = await CosmosDBHelpers.ReadDocument(expectedDocId); @@ -40,6 +42,117 @@ public async Task CosmosDBTriggerAndOutput_Succeeds() } } + [Theory] + [InlineData("DocsByUsingCosmosClient")] + [InlineData("DocsByUsingDatabaseClient")] + [InlineData("DocsByUsingContainerClient")] + public async Task CosmosInput_ClientBinding_Succeeds(string functionName) + { + string expectedDocId = Guid.NewGuid().ToString(); + try + { + //Setup + await CosmosDBHelpers.CreateDocument(expectedDocId, expectedDocId); + + //Trigger + HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionName); + string actualMessage = await response.Content.ReadAsStringAsync(); + + //Verify + HttpStatusCode expectedStatusCode = HttpStatusCode.OK; + + Assert.Equal(expectedStatusCode, response.StatusCode); + Assert.Contains(expectedDocId, actualMessage); + } + finally + { + //Clean up + await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); + } + } + + [Fact] + public async Task CosmosInput_DocByIdFromRouteData_Succeeds() + { + string expectedDocId = Guid.NewGuid().ToString(); + string functionPath = $"docsbyroute/{expectedDocId}/{expectedDocId}"; + try + { + //Setup + await CosmosDBHelpers.CreateDocument(expectedDocId, "DocByIdFromRouteData"); + + //Trigger + HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionPath); + string actualMessage = await response.Content.ReadAsStringAsync(); + + //Verify + HttpStatusCode expectedStatusCode = HttpStatusCode.OK; + + Assert.Equal(expectedStatusCode, response.StatusCode); + Assert.Contains("DocByIdFromRouteData", actualMessage); + } + finally + { + //Clean up + await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); + } + } + + [Fact] + public async Task CosmosInput_DocByIdFromRouteDataUsingSqlQuery_Succeeds() + { + string expectedDocId = Guid.NewGuid().ToString(); + string functionPath = $"docsbysql/{expectedDocId}"; + try + { + //Setup + await CosmosDBHelpers.CreateDocument(expectedDocId, "DocByIdFromRouteDataUsingSqlQuery"); + + //Trigger + HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionPath); + string actualMessage = await response.Content.ReadAsStringAsync(); + + //Verify + HttpStatusCode expectedStatusCode = HttpStatusCode.OK; + + Assert.Equal(expectedStatusCode, response.StatusCode); + Assert.Contains("DocByIdFromRouteDataUsingSqlQuery", actualMessage); + } + finally + { + //Clean up + await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); + } + } + + [Fact] + public async Task CosmosInput_DocByIdFromQueryStringUsingSqlQuery_Succeeds() + { + string expectedDocId = Guid.NewGuid().ToString(); + string functionName = "DocByIdFromQueryStringUsingSqlQuery"; + string requestBody = @$"{{ ""id"": ""{expectedDocId}"" }}"; + try + { + //Setup + await CosmosDBHelpers.CreateDocument(expectedDocId, functionName); + + //Trigger + HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody(functionName, requestBody, "application/json"); + string actualMessage = await response.Content.ReadAsStringAsync(); + + //Verify + HttpStatusCode expectedStatusCode = HttpStatusCode.OK; + + Assert.Equal(expectedStatusCode, response.StatusCode); + Assert.Contains(functionName, actualMessage); + } + finally + { + //Clean up + await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); + } + } + public void Dispose() { _disposeLog?.Dispose(); diff --git a/test/E2ETests/E2ETests/E2ETests.csproj b/test/E2ETests/E2ETests/E2ETests.csproj index f7cbb5240..75e1d31eb 100644 --- a/test/E2ETests/E2ETests/E2ETests.csproj +++ b/test/E2ETests/E2ETests/E2ETests.csproj @@ -8,6 +8,7 @@ + @@ -19,7 +20,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs b/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs index 41a164a21..524864b53 100644 --- a/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs +++ b/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs @@ -90,6 +90,13 @@ await TestUtility.RetryAsync(async () => } catch { + if (_funcProcess.HasExited) + { + // Something went wrong starting the host - check the logs + _logger.LogInformation($" Current state: process exited - something may have gone wrong."); + return false; + } + // Can get exceptions before host is running. _logger.LogInformation($" Current state: process starting"); return false; diff --git a/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs b/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs index 92d512d23..f146c3396 100644 --- a/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs +++ b/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs @@ -28,12 +28,10 @@ static CosmosDBHelpers() } // keep - public async static Task CreateDocument(string docId) + public async static Task CreateDocument(string docId, string docText = "test") { - Document documentToTest = new Document() - { - Id = docId - }; + Document documentToTest = new Document() { Id = docId }; + documentToTest.SetPropertyValue("Text", docText); _ = await _docDbClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName), documentToTest); } @@ -63,16 +61,16 @@ await TestUtility.RetryAsync(async () => public async static Task DeleteTestDocuments(string docId) { var inputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName, docId); - await DeleteDocument(inputDocUri); + await DeleteDocument(inputDocUri, docId); var outputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId); - await DeleteDocument(outputDocUri); + await DeleteDocument(outputDocUri, docId); } - private async static Task DeleteDocument(Uri docUri) + private async static Task DeleteDocument(Uri docUri, string docId) { try { - await _docDbClient.DeleteDocumentAsync(docUri); + await _docDbClient.DeleteDocumentAsync(docUri, new RequestOptions { PartitionKey = new PartitionKey(docId) }); } catch (Exception) { diff --git a/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs b/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs index a7fd68ed6..d5f788498 100644 --- a/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs +++ b/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs @@ -19,7 +19,7 @@ public static async Task InvokeHttpTrigger(string functionN return await GetResponseMessage(request); } - public static async Task InvokeHttpTriggerWithBody(string functionName, string body, HttpStatusCode expectedStatusCode, string mediaType, int expectedCode = 0) + public static async Task InvokeHttpTriggerWithBody(string functionName, string body, string mediaType) { HttpRequestMessage request = GetTestRequest(functionName); request.Content = new StringContent(body); diff --git a/test/E2ETests/E2ETests/HttpEndToEndTests.cs b/test/E2ETests/E2ETests/HttpEndToEndTests.cs index 1ba7dd234..244680ac7 100644 --- a/test/E2ETests/E2ETests/HttpEndToEndTests.cs +++ b/test/E2ETests/E2ETests/HttpEndToEndTests.cs @@ -48,7 +48,7 @@ public async Task HttpTriggerTests(string functionName, string queryString, Http [InlineData("HelloFromJsonBody", "{\"Name\": \"Bob\"}", "application/octet-stream", HttpStatusCode.OK, "Hello Bob")] public async Task HttpTriggerTestsMediaTypeDoNotMatter(string functionName, string body, string mediaType, HttpStatusCode expectedStatusCode, string expectedBody) { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody(functionName, body, expectedStatusCode, mediaType); + HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody(functionName, body, mediaType); string responseBody = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedStatusCode, response.StatusCode); @@ -58,7 +58,7 @@ public async Task HttpTriggerTestsMediaTypeDoNotMatter(string functionName, stri [Fact] public async Task HttpTriggerTestsPocoResult() { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody("HelloUsingPoco", string.Empty, HttpStatusCode.OK, "application/json"); + HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody("HelloUsingPoco", string.Empty, "application/json"); string responseBody = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -68,7 +68,7 @@ public async Task HttpTriggerTestsPocoResult() [Fact(Skip = "Proxies not currently supported in V4 but will be coming back.")] public async Task HttpProxy() { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody("proxytest", string.Empty, HttpStatusCode.OK, "application/json"); + HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody("proxytest", string.Empty, "application/json"); string responseBody = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.OK, response.StatusCode);