public void GetFunctionInvokeUrlTemplate_ReturnsExpectedResult() { string baseUrl = "https://localhost"; var functionMetadata = new FunctionMetadata { Name = "TestFunction" }; var httpTriggerBinding = new BindingMetadata { Name = "req", Type = "httpTrigger", Direction = BindingDirection.In, Raw = new JObject() }; functionMetadata.Bindings.Add(httpTriggerBinding); var uri = FunctionMetadataExtensions.GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, "api"); Assert.Equal("https://localhost/api/testfunction", uri.ToString()); // with empty route prefix uri = FunctionMetadataExtensions.GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, string.Empty); Assert.Equal("https://localhost/testfunction", uri.ToString()); // with a custom route httpTriggerBinding.Raw.Add("route", "catalog/products/{category:alpha?}/{id:int?}"); uri = FunctionMetadataExtensions.GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, "api"); Assert.Equal("https://localhost/api/catalog/products/{category:alpha?}/{id:int?}", uri.ToString()); // with empty route prefix uri = FunctionMetadataExtensions.GetFunctionInvokeUrlTemplate(baseUrl, functionMetadata, string.Empty); Assert.Equal("https://localhost/catalog/products/{category:alpha?}/{id:int?}", uri.ToString()); }
/// <summary> /// It handles creating a new function or updating an existing one. /// It attempts to clean left over artifacts from a possible previous function with the same name /// if config is changed, then `configChanged` is set to true so the caller can call SyncTriggers if needed. /// </summary> /// <param name="name">name of the function to be created</param> /// <param name="functionMetadata">in case of update for function.json</param> /// <param name="request">Current HttpRequest.</param> /// <returns>(success, configChanged, functionMetadataResult)</returns> public async Task <(bool, bool, FunctionMetadataResponse)> CreateOrUpdate(string name, FunctionMetadataResponse functionMetadata, HttpRequest request) { var configChanged = false; var functionDir = Path.Combine(_config.RootScriptPath, name); // Make sure the function folder exists if (!FileUtility.DirectoryExists(functionDir)) { // Cleanup any leftover artifacts from a function with the same name before. DeleteFunctionArtifacts(functionMetadata); Directory.CreateDirectory(functionDir); } string newConfig = null; string configPath = Path.Combine(functionDir, ScriptConstants.FunctionMetadataFileName); string dataFilePath = FunctionMetadataExtensions.GetTestDataFilePath(name, _config); // If files are included, write them out if (functionMetadata?.Files != null) { // If the config is passed in the file collection, save it and don't process it as a file if (functionMetadata.Files.TryGetValue(ScriptConstants.FunctionMetadataFileName, out newConfig)) { functionMetadata.Files.Remove(ScriptConstants.FunctionMetadataFileName); } // Delete all existing files in the directory. This will also delete current function.json, but it gets recreated below FileUtility.DeleteDirectoryContentsSafe(functionDir); await functionMetadata .Files .Select(e => FileUtility.WriteAsync(Path.Combine(functionDir, e.Key), e.Value)) .WhenAll(); } // Get the config (if it was not already passed in as a file) if (newConfig == null && functionMetadata?.Config != null) { newConfig = JsonConvert.SerializeObject(functionMetadata?.Config, Formatting.Indented); } // Get the current config, if any string currentConfig = null; if (FileUtility.FileExists(configPath)) { currentConfig = await FileUtility.ReadAsync(configPath); } // Save the file and set changed flag is it has changed. This helps optimize the syncTriggers call if (newConfig != currentConfig) { await FileUtility.WriteAsync(configPath, newConfig); configChanged = true; } if (functionMetadata.TestData != null) { await FileUtility.WriteAsync(dataFilePath, functionMetadata.TestData); } (var success, var functionMetadataResult) = await TryGetFunction(name, request); // test_data took from incoming request, it will not exceed the limit return(success, configChanged, functionMetadataResult); }