diff --git a/src/Azure.Functions.PowerShell.Worker.csproj b/src/Azure.Functions.PowerShell.Worker.csproj index 8773f1fc..03feccdf 100644 --- a/src/Azure.Functions.PowerShell.Worker.csproj +++ b/src/Azure.Functions.PowerShell.Worker.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Function/FunctionInfo.cs b/src/Function/FunctionInfo.cs deleted file mode 100644 index b9b0ee38..00000000 --- a/src/Function/FunctionInfo.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using Google.Protobuf.Collections; -using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - -namespace Microsoft.Azure.Functions.PowerShellWorker -{ - internal class FunctionInfo - { - public string Directory {get; set;} - public string HttpOutputName {get; set;} - public string Name {get; set;} - public MapField Bindings { get; } = new MapField(); - public MapField OutputBindings { get; } = new MapField(); - - public FunctionInfo() { } - - public FunctionInfo(RpcFunctionMetadata metadata) - { - Name = metadata.Name; - Directory = metadata.Directory; - HttpOutputName = ""; - - foreach (var binding in metadata.Bindings) - { - Bindings.Add(binding.Key, binding.Value); - - // Only add Out and InOut bindings to the OutputBindings - if (binding.Value.Direction != BindingInfo.Types.Direction.In) - { - if(binding.Value.Type == "http") - { - HttpOutputName = binding.Key; - } - OutputBindings.Add(binding.Key, binding.Value); - } - } - } - } -} diff --git a/src/Function/FunctionLoader.cs b/src/Function/FunctionLoader.cs deleted file mode 100644 index dd6df5fc..00000000 --- a/src/Function/FunctionLoader.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using Google.Protobuf.Collections; -using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - -namespace Microsoft.Azure.Functions.PowerShellWorker -{ - internal class FunctionLoader - { - readonly MapField _LoadedFunctions = new MapField(); - - public (string ScriptPath, string EntryPoint) GetFunc(string functionId) => - (_LoadedFunctions[functionId].ScriptPath, _LoadedFunctions[functionId].EntryPoint); - - public FunctionInfo GetInfo(string functionId) => _LoadedFunctions[functionId].Info; - - public void Load(string functionId, RpcFunctionMetadata metadata) - { - // TODO: catch "load" issues at "func start" time. - // ex. Script doesn't exist, entry point doesn't exist - _LoadedFunctions.Add(functionId, new Function - { - Info = new FunctionInfo(metadata), - ScriptPath = metadata.ScriptFile, - EntryPoint = metadata.EntryPoint - }); - } - } - - internal class Function - { - public string EntryPoint {get; internal set;} - public FunctionInfo Info {get; internal set;} - public string ScriptPath {get; internal set;} - } -} diff --git a/src/FunctionLoader.cs b/src/FunctionLoader.cs new file mode 100644 index 00000000..c1f7c666 --- /dev/null +++ b/src/FunctionLoader.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using Google.Protobuf.Collections; +using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + +namespace Microsoft.Azure.Functions.PowerShellWorker +{ + internal class FunctionLoader + { + private readonly MapField _loadedFunctions = new MapField(); + + internal FunctionInfo GetFunctionInfo(string functionId) + { + if (_loadedFunctions.TryGetValue(functionId, out FunctionInfo funcInfo)) + { + return funcInfo; + } + + throw new InvalidOperationException($"Function with the ID '{functionId}' was not loaded."); + } + + internal void Load(FunctionLoadRequest request) + { + // TODO: catch "load" issues at "func start" time. + // ex. Script doesn't exist, entry point doesn't exist + _loadedFunctions.Add(request.FunctionId, new FunctionInfo(request.Metadata)); + } + } + + internal class FunctionInfo + { + internal readonly string Directory; + internal readonly string EntryPoint; + internal readonly string FunctionName; + internal readonly string ScriptPath; + internal readonly MapField AllBindings; + internal readonly MapField OutputBindings; + + public FunctionInfo(RpcFunctionMetadata metadata) + { + FunctionName = metadata.Name; + Directory = metadata.Directory; + EntryPoint = metadata.EntryPoint; + ScriptPath = metadata.ScriptFile; + + AllBindings = new MapField(); + OutputBindings = new MapField(); + + foreach (var binding in metadata.Bindings) + { + AllBindings.Add(binding.Key, binding.Value); + + // PowerShell doesn't support the 'InOut' type binding + if (binding.Value.Direction == BindingInfo.Types.Direction.Out) + { + OutputBindings.Add(binding.Key, binding.Value); + } + } + } + } +} diff --git a/src/Http/HttpRequestContext.cs b/src/Http/HttpRequestContext.cs index 3448e6d6..316b89a7 100644 --- a/src/Http/HttpRequestContext.cs +++ b/src/Http/HttpRequestContext.cs @@ -4,6 +4,7 @@ // using System; +using System.Collections.Generic; using Google.Protobuf.Collections; namespace Microsoft.Azure.Functions.PowerShellWorker @@ -11,8 +12,18 @@ namespace Microsoft.Azure.Functions.PowerShellWorker /// /// Custom type represent the context of the in-coming Http request. /// - public class HttpRequestContext : IEquatable + public class HttpRequestContext { + /// + /// Constructor for HttpRequestContext. + /// + public HttpRequestContext() + { + Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + Params = new Dictionary(StringComparer.OrdinalIgnoreCase); + Query = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + /// /// Gets the Body of the Http request. /// @@ -21,7 +32,7 @@ public class HttpRequestContext : IEquatable /// /// Gets the Headers of the Http request. /// - public MapField Headers { get; internal set; } + public Dictionary Headers { get; private set; } /// /// Gets the Method of the Http request. @@ -36,30 +47,16 @@ public class HttpRequestContext : IEquatable /// /// Gets the Params of the Http request. /// - public MapField Params { get; internal set; } + public Dictionary Params { get; private set; } /// /// Gets the Query of the Http request. /// - public MapField Query { get; internal set; } + public Dictionary Query { get; private set; } /// /// Gets the RawBody of the Http request. /// public object RawBody { get; internal set; } - - /// - /// Compare with another HttpRequestContext object. - /// - public bool Equals(HttpRequestContext other) - { - return Method == other.Method - && Url == other.Url - && Headers.Equals(other.Headers) - && Params.Equals(other.Params) - && Query.Equals(other.Query) - && (Body == other.Body || Body.Equals(other.Body)) - && (RawBody == other.RawBody || RawBody.Equals(other.RawBody)); - } } } diff --git a/src/Http/HttpResponseContext.cs b/src/Http/HttpResponseContext.cs index 2b62bc21..d7eb5c80 100644 --- a/src/Http/HttpResponseContext.cs +++ b/src/Http/HttpResponseContext.cs @@ -4,14 +4,14 @@ // using System; -using System.Collections; +using System.Collections.Generic; namespace Microsoft.Azure.Functions.PowerShellWorker { /// /// Custom type represent the context of the Http response. /// - public class HttpResponseContext : IEquatable + public class HttpResponseContext { /// /// Gets or sets the Body of the Http response. @@ -31,34 +31,11 @@ public class HttpResponseContext : IEquatable /// /// Gets or sets the Headers of the Http response. /// - public Hashtable Headers { get; set; } = new Hashtable(); + public Dictionary Headers { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets or sets the StatusCode of the Http response. /// public string StatusCode { get; set; } = "200"; - - /// - /// Compare with another HttpResponseContext object. - /// - public bool Equals(HttpResponseContext other) - { - bool sameHeaders = true; - foreach (DictionaryEntry dictionaryEntry in Headers) - { - if (!other.Headers.ContainsKey(dictionaryEntry.Key) - || dictionaryEntry.Value != other.Headers[dictionaryEntry.Key]) - { - sameHeaders = false; - break; - } - } - - return ContentType == other.ContentType - && EnableContentNegotiation == other.EnableContentNegotiation - && StatusCode == other.StatusCode - && sameHeaders - && (Body == other.Body || Body.Equals(other.Body)); - } } } diff --git a/src/Messaging/MessagingStream.cs b/src/Messaging/MessagingStream.cs index 23b1fa24..6b3bb199 100644 --- a/src/Messaging/MessagingStream.cs +++ b/src/Messaging/MessagingStream.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Messaging { internal class MessagingStream : IDisposable { - private SemaphoreSlim _writeStreamHandle = new SemaphoreSlim(1, 1); + private SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1); private AsyncDuplexStreamingCall _call; private bool isDisposed; @@ -45,14 +45,14 @@ public async Task WriteAsync(StreamingMessage message) // Wait for the handle to be released because we can't have // more than one message being sent at the same time - await _writeStreamHandle.WaitAsync(); + await _writeSemaphore.WaitAsync(); try { await _call.RequestStream.WriteAsync(message); } finally { - _writeStreamHandle.Release(); + _writeSemaphore.Release(); } } } diff --git a/src/Messaging/RpcLogger.cs b/src/Messaging/RpcLogger.cs index a790afb2..9677d3b6 100644 --- a/src/Messaging/RpcLogger.cs +++ b/src/Messaging/RpcLogger.cs @@ -7,72 +7,49 @@ using Microsoft.Azure.Functions.PowerShellWorker.Messaging; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; -using Microsoft.Extensions.Logging; +using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level; namespace Microsoft.Azure.Functions.PowerShellWorker.Utility { - internal class RpcLogger : ILogger + internal class RpcLogger : IDisposable { private MessagingStream _msgStream; - private string _invocationId = ""; - private string _requestId = ""; + private string _invocationId; + private string _requestId; public RpcLogger(MessagingStream msgStream) { _msgStream = msgStream; } - public IDisposable BeginScope(TState state) => - throw new NotImplementedException(); - - public static RpcLog.Types.Level ConvertLogLevel(LogLevel logLevel) + public IDisposable BeginScope(string requestId, string invocationId) { - switch (logLevel) - { - case LogLevel.Critical: - return RpcLog.Types.Level.Critical; - case LogLevel.Debug: - return RpcLog.Types.Level.Debug; - case LogLevel.Error: - return RpcLog.Types.Level.Error; - case LogLevel.Information: - return RpcLog.Types.Level.Information; - case LogLevel.Trace: - return RpcLog.Types.Level.Trace; - case LogLevel.Warning: - return RpcLog.Types.Level.Warning; - default: - return RpcLog.Types.Level.None; - } + _requestId = requestId; + _invocationId = invocationId; + return this; } - public bool IsEnabled(LogLevel logLevel) => - throw new NotImplementedException(); - - public async void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + public void Dispose() { - if (_msgStream != null) - { - var logMessage = new StreamingMessage - { - RequestId = _requestId, - RpcLog = new RpcLog() - { - Exception = exception?.ToRpcException(), - InvocationId = _invocationId, - Level = ConvertLogLevel(logLevel), - Message = formatter(state, exception) - } - }; - - await _msgStream.WriteAsync(logMessage); - } + _requestId = null; + _invocationId = null; } - public void SetContext(string requestId, string invocationId) + public async void Log(LogLevel logLevel, string message, Exception exception = null) { - _requestId = requestId; - _invocationId = invocationId; + var logMessage = new StreamingMessage + { + RequestId = _requestId, + RpcLog = new RpcLog() + { + Exception = exception?.ToRpcException(), + InvocationId = _invocationId, + Level = logLevel, + Message = message + } + }; + + await _msgStream.WriteAsync(logMessage); } } } diff --git a/src/PowerShell/PowerShellManager.cs b/src/PowerShell/PowerShellManager.cs index 1f30a760..b2500df3 100644 --- a/src/PowerShell/PowerShellManager.cs +++ b/src/PowerShell/PowerShellManager.cs @@ -11,8 +11,8 @@ using Microsoft.Azure.Functions.PowerShellWorker.Utility; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; -using Microsoft.Extensions.Logging; using System.Management.Automation.Runspaces; +using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level; namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell { @@ -98,7 +98,7 @@ internal Hashtable InvokeFunction( if(parameterMetadata.ContainsKey(_TriggerMetadataParameterName)) { _pwsh.AddParameter(_TriggerMetadataParameterName, triggerMetadata); - _logger.LogDebug($"TriggerMetadata found. Value:{Environment.NewLine}{triggerMetadata.ToString()}"); + _logger.Log(LogLevel.Debug, $"TriggerMetadata found. Value:{Environment.NewLine}{triggerMetadata.ToString()}"); } PSObject returnObject = null; @@ -108,7 +108,7 @@ internal Hashtable InvokeFunction( Collection pipelineItems = _pwsh.InvokeAndClearCommands(); foreach (var psobject in pipelineItems) { - _logger.LogInformation($"OUTPUT: {psobject.ToString()}"); + _logger.Log(LogLevel.Information, $"OUTPUT: {psobject.ToString()}"); } returnObject = pipelineItems[pipelineItems.Count - 1]; diff --git a/src/PowerShell/StreamHandler.cs b/src/PowerShell/StreamHandler.cs index e6064d5f..2fea5894 100644 --- a/src/PowerShell/StreamHandler.cs +++ b/src/PowerShell/StreamHandler.cs @@ -4,7 +4,7 @@ // using Microsoft.Azure.Functions.PowerShellWorker.Utility; -using Microsoft.Extensions.Logging; +using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level; namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell { @@ -23,7 +23,7 @@ public void DebugDataAdding(object sender, DataAddingEventArgs e) { if(e.ItemAdded is DebugRecord record) { - _logger.LogDebug($"DEBUG: {record.Message}"); + _logger.Log(LogLevel.Debug, $"DEBUG: {record.Message}"); } } @@ -31,7 +31,7 @@ public void ErrorDataAdding(object sender, DataAddingEventArgs e) { if(e.ItemAdded is ErrorRecord record) { - _logger.LogError(record.Exception, $"ERROR: {record.Exception.Message}"); + _logger.Log(LogLevel.Error, $"ERROR: {record.Exception.Message}", record.Exception); } } @@ -39,7 +39,7 @@ public void InformationDataAdding(object sender, DataAddingEventArgs e) { if(e.ItemAdded is InformationRecord record) { - _logger.LogInformation($"INFORMATION: {record.MessageData}"); + _logger.Log(LogLevel.Information, $"INFORMATION: {record.MessageData}"); } } @@ -47,7 +47,7 @@ public void ProgressDataAdding(object sender, DataAddingEventArgs e) { if(e.ItemAdded is ProgressRecord record) { - _logger.LogTrace($"PROGRESS: {record.StatusDescription}"); + _logger.Log(LogLevel.Trace, $"PROGRESS: {record.StatusDescription}"); } } @@ -55,7 +55,7 @@ public void VerboseDataAdding(object sender, DataAddingEventArgs e) { if(e.ItemAdded is VerboseRecord record) { - _logger.LogTrace($"VERBOSE: {record.Message}"); + _logger.Log(LogLevel.Trace, $"VERBOSE: {record.Message}"); } } @@ -63,7 +63,7 @@ public void WarningDataAdding(object sender, DataAddingEventArgs e) { if(e.ItemAdded is WarningRecord record) { - _logger.LogWarning($"WARNING: {record.Message}"); + _logger.Log(LogLevel.Warning, $"WARNING: {record.Message}"); } } } diff --git a/src/RequestProcessor.cs b/src/RequestProcessor.cs index 9398d235..36b22470 100644 --- a/src/RequestProcessor.cs +++ b/src/RequestProcessor.cs @@ -101,7 +101,7 @@ internal StreamingMessage ProcessFunctionLoadRequest(StreamingMessage request) // Try to load the functions try { - _functionLoader.Load(functionLoadRequest.FunctionId, functionLoadRequest.Metadata); + _functionLoader.Load(functionLoadRequest); } catch (Exception e) { @@ -124,20 +124,6 @@ internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) { InvocationRequest invocationRequest = request.InvocationRequest; - // Set the RequestId and InvocationId for logging purposes - _logger.SetContext(request.RequestId, invocationRequest.InvocationId); - - // Load information about the function - var functionInfo = _functionLoader.GetInfo(invocationRequest.FunctionId); - (string scriptPath, string entryPoint) = _functionLoader.GetFunc(invocationRequest.FunctionId); - - // Bundle all TriggerMetadata into Hashtable to send down to PowerShell - Hashtable triggerMetadata = new Hashtable(); - foreach (var dataItem in invocationRequest.TriggerMetadata) - { - triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); - } - // Assume success unless something bad happens var status = new StatusResult() { Status = StatusResult.Types.Status.Success }; var response = new StreamingMessage() @@ -151,36 +137,58 @@ internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) }; // Invoke powershell logic and return hashtable of out binding data - Hashtable result = null; try { - result = _powerShellManager - .InvokeFunction(scriptPath, entryPoint, triggerMetadata, invocationRequest.InputData); - } - catch (Exception e) - { - status.Status = StatusResult.Types.Status.Failure; - status.Exception = e.ToRpcException(); - return response; - } + // Load information about the function + var functionInfo = _functionLoader.GetFunctionInfo(invocationRequest.FunctionId); - // Set out binding data and return response to be sent back to host - foreach (KeyValuePair binding in functionInfo.OutputBindings) - { - ParameterBinding paramBinding = new ParameterBinding() + // Bundle all TriggerMetadata into Hashtable to send down to PowerShell + var triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase); + foreach (var dataItem in invocationRequest.TriggerMetadata) { - Name = binding.Key, - Data = result[binding.Key].ToTypedData() - }; + // MapField is case-sensitive, but powershell is case-insensitive, + // so for keys differ only in casing, the first wins. + if (!triggerMetadata.ContainsKey(dataItem.Key)) + { + triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); + } + } - response.InvocationResponse.OutputData.Add(paramBinding); + // Set the RequestId and InvocationId for logging purposes + Hashtable result = null; + using (_logger.BeginScope(request.RequestId, invocationRequest.InvocationId)) + { + result = _powerShellManager.InvokeFunction( + functionInfo.ScriptPath, + functionInfo.EntryPoint, + triggerMetadata, + invocationRequest.InputData); + } - // if one of the bindings is $return we need to also set the ReturnValue - if(binding.Key == "$return") + // Set out binding data and return response to be sent back to host + foreach (KeyValuePair binding in functionInfo.OutputBindings) { - response.InvocationResponse.ReturnValue = paramBinding.Data; + // if one of the bindings is '$return' we need to set the ReturnValue + if(string.Equals(binding.Key, "$return", StringComparison.OrdinalIgnoreCase)) + { + response.InvocationResponse.ReturnValue = result[binding.Key].ToTypedData(); + continue; + } + + ParameterBinding paramBinding = new ParameterBinding() + { + Name = binding.Key, + Data = result[binding.Key].ToTypedData() + }; + + response.InvocationResponse.OutputData.Add(paramBinding); } } + catch (Exception e) + { + status.Status = StatusResult.Types.Status.Failure; + status.Exception = e.ToRpcException(); + } return response; } diff --git a/src/Utility/ExecutionTimer.cs b/src/Utility/ExecutionTimer.cs index 35c5ac0b..f107c36e 100644 --- a/src/Utility/ExecutionTimer.cs +++ b/src/Utility/ExecutionTimer.cs @@ -6,8 +6,7 @@ using System; using System.Diagnostics; using System.Text; - -using Microsoft.Extensions.Logging; +using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level; namespace Microsoft.Azure.Functions.PowerShellWorker.Utility { @@ -71,7 +70,7 @@ public void Dispose() .Append("ms]") .ToString(); - _logger.LogTrace(logMessage); + _logger.Log(LogLevel.Trace, logMessage); s_stopwatch.Reset(); } diff --git a/src/Utility/TypeExtensions.cs b/src/Utility/TypeExtensions.cs index e55c1687..b469798b 100644 --- a/src/Utility/TypeExtensions.cs +++ b/src/Utility/TypeExtensions.cs @@ -20,12 +20,33 @@ static HttpRequestContext ToHttpRequestContext (this RpcHttp rpcHttp) var httpRequestContext = new HttpRequestContext { Method = rpcHttp.Method, - Url = rpcHttp.Url, - Headers = rpcHttp.Headers, - Params = rpcHttp.Params, - Query = rpcHttp.Query + Url = rpcHttp.Url }; + if (rpcHttp.Headers != null) + { + foreach (var pair in rpcHttp.Headers) + { + httpRequestContext.Headers.TryAdd(pair.Key, pair.Value); + } + } + + if (rpcHttp.Params != null) + { + foreach (var pair in rpcHttp.Params) + { + httpRequestContext.Params.TryAdd(pair.Key, pair.Value); + } + } + + if (rpcHttp.Query != null) + { + foreach (var pair in rpcHttp.Query) + { + httpRequestContext.Query.TryAdd(pair.Key, pair.Value); + } + } + if (rpcHttp.Body != null) { httpRequestContext.Body = rpcHttp.Body.ToObject(); @@ -73,9 +94,9 @@ public static RpcException ToRpcException (this Exception exception) { return new RpcException { - Message = exception?.Message, - Source = exception?.Source ?? "", - StackTrace = exception?.StackTrace ?? "" + Message = exception.Message, + Source = exception.Source ?? "", + StackTrace = exception.StackTrace ?? "" }; } @@ -92,9 +113,9 @@ static RpcHttp ToRpcHttp (this HttpResponseContext httpResponseContext) } // Add all the headers. ContentType is separated for convenience - foreach (DictionaryEntry item in httpResponseContext.Headers) + foreach (var item in httpResponseContext.Headers) { - rpcHttp.Headers.Add(item.Key.ToString(), item.Value.ToString()); + rpcHttp.Headers.Add(item.Key, item.Value); } // Allow the user to set content-type in the Headers @@ -123,9 +144,9 @@ public static TypedData ToTypedData(this object value) { typedData.Http = http.ToRpcHttp(); } - else if (LanguagePrimitives.TryConvertTo(value, out Hashtable hashtable)) + else if (LanguagePrimitives.TryConvertTo(value, out IDictionary hashtable)) { - typedData.Json = JsonConvert.SerializeObject(hashtable); + typedData.Json = JsonConvert.SerializeObject(hashtable); } else if (LanguagePrimitives.TryConvertTo(value, out string str)) { diff --git a/test/Function/FunctionLoaderTests.cs b/test/Function/FunctionLoaderTests.cs index 6fe116d3..a5b6bd3d 100644 --- a/test/Function/FunctionLoaderTests.cs +++ b/test/Function/FunctionLoaderTests.cs @@ -36,13 +36,18 @@ public void TestFunctionLoaderGetFunc() Type = "http" }); + var functionLoadRequest = new FunctionLoadRequest{ + FunctionId = functionId, + Metadata = metadata + }; + var functionLoader = new FunctionLoader(); - functionLoader.Load(functionId, metadata); + functionLoader.Load(functionLoadRequest); - (string scriptPathResult, string entryPointResult) = functionLoader.GetFunc(functionId); + var funcInfo = functionLoader.GetFunctionInfo(functionId); - Assert.Equal(scriptPathExpected, scriptPathResult); - Assert.Equal("", entryPointResult); + Assert.Equal(scriptPathExpected, funcInfo.ScriptPath); + Assert.Equal("", funcInfo.EntryPoint); } [Fact] @@ -70,13 +75,18 @@ public void TestFunctionLoaderGetFuncWithEntryPoint() Type = "http" }); + var functionLoadRequest = new FunctionLoadRequest{ + FunctionId = functionId, + Metadata = metadata + }; + var functionLoader = new FunctionLoader(); - functionLoader.Load(functionId, metadata); + functionLoader.Load(functionLoadRequest); - (string scriptPathResult, string entryPointResult) = functionLoader.GetFunc(functionId); + var funcInfo = functionLoader.GetFunctionInfo(functionId); - Assert.Equal(scriptPathExpected, scriptPathResult); - Assert.Equal(entryPointExpected, entryPointResult); + Assert.Equal(scriptPathExpected, funcInfo.ScriptPath); + Assert.Equal(entryPointExpected, funcInfo.EntryPoint); } [Fact] @@ -104,39 +114,20 @@ public void TestFunctionLoaderGetInfo() Type = "http" }); - var infoExpected = new FunctionInfo - { - Directory = directory, - HttpOutputName = "", - Name = name + var functionLoadRequest = new FunctionLoadRequest{ + FunctionId = functionId, + Metadata = metadata }; - infoExpected.Bindings.Add("req", new BindingInfo - { - Direction = BindingInfo.Types.Direction.In, - Type = "httpTrigger" - }); - infoExpected.Bindings.Add("res", new BindingInfo - { - Direction = BindingInfo.Types.Direction.Out, - Type = "http" - }); - - infoExpected.OutputBindings.Add("res", new BindingInfo - { - Direction = BindingInfo.Types.Direction.Out, - Type = "http" - }); var functionLoader = new FunctionLoader(); - functionLoader.Load(functionId, metadata); + functionLoader.Load(functionLoadRequest); - var infoResult = functionLoader.GetInfo(functionId); + var funcInfo = functionLoader.GetFunctionInfo(functionId); - Assert.Equal(directory, infoResult.Directory); - Assert.Equal("res", infoResult.HttpOutputName); - Assert.Equal(name, infoResult.Name); - Assert.Equal(infoExpected.Bindings.Count, infoResult.Bindings.Count); - Assert.Equal(infoExpected.OutputBindings.Count, infoResult.OutputBindings.Count); + Assert.Equal(directory, funcInfo.Directory); + Assert.Equal(name, funcInfo.FunctionName); + Assert.Equal(2, funcInfo.AllBindings.Count); + Assert.Equal(1, funcInfo.OutBindings.Count); } } } diff --git a/test/Utility/TypeExtensionsTests.cs b/test/Utility/TypeExtensionsTests.cs index ea929f13..73ff1c8b 100644 --- a/test/Utility/TypeExtensionsTests.cs +++ b/test/Utility/TypeExtensionsTests.cs @@ -34,16 +34,15 @@ public void TestTypedDataToObjectHttpRequestContextBasic() } }; - var expected = new HttpRequestContext - { - Method = method, - Url = url, - Headers = new MapField(), - Params = new MapField(), - Query = new MapField() - }; - - Assert.Equal(expected, (HttpRequestContext)input.ToObject()); + var httpRequestContext = (HttpRequestContext)input.ToObject(); + + Assert.Equal(httpRequestContext.Method, method); + Assert.Equal(httpRequestContext.Url, url); + Assert.Null(httpRequestContext.Body); + Assert.Null(httpRequestContext.RawBody); + Assert.Empty(httpRequestContext.Headers); + Assert.Empty(httpRequestContext.Params); + Assert.Empty(httpRequestContext.Query); } [Fact] @@ -66,25 +65,20 @@ public void TestTypedDataToObjectHttpRequestContextWithUrlData() input.Http.Params.Add(key, value); input.Http.Query.Add(key, value); - var expected = new HttpRequestContext - { - Method = method, - Url = url, - Headers = new MapField - { - {key, value} - }, - Params = new MapField - { - {key, value} - }, - Query = new MapField - { - {key, value} - } - }; + var httpRequestContext = (HttpRequestContext)input.ToObject(); + + Assert.Equal(httpRequestContext.Method, method); + Assert.Equal(httpRequestContext.Url, url); + Assert.Null(httpRequestContext.Body); + Assert.Null(httpRequestContext.RawBody); - Assert.Equal(expected, (HttpRequestContext)input.ToObject()); + Assert.Single(httpRequestContext.Headers); + Assert.Single(httpRequestContext.Params); + Assert.Single(httpRequestContext.Query); + + Assert.Equal(httpRequestContext.Headers[key], value); + Assert.Equal(httpRequestContext.Params[key], value); + Assert.Equal(httpRequestContext.Query[key], value); } [Fact] @@ -115,14 +109,19 @@ public void TestTypedDataToObjectHttpRequestContextBodyData() { Method = method, Url = url, - Headers = new MapField(), - Params = new MapField(), - Query = new MapField(), Body = data, RawBody = data }; - Assert.Equal(expected, (HttpRequestContext)input.ToObject()); + var httpRequestContext = (HttpRequestContext)input.ToObject(); + + Assert.Equal(httpRequestContext.Method, method); + Assert.Equal(httpRequestContext.Url, url); + Assert.Equal(httpRequestContext.Body, data); + Assert.Equal(httpRequestContext.RawBody, data); + Assert.Empty(httpRequestContext.Headers); + Assert.Empty(httpRequestContext.Params); + Assert.Empty(httpRequestContext.Query); } [Fact]