From 7ca70309cbc626930521be0684a5dc3fb0f4d792 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 22 Jun 2023 10:35:21 +0200 Subject: [PATCH] Support setting WireMockServerSettings via Environment (#954) * Support parsing environment variables (WireMockServerSettings__) * case ignore * fix * SimpleSettingsParserTests * . * int * more test --- .../Program.cs | 2 +- src/WireMock.Net.StandAlone/StandAloneApp.cs | 9 +- .../Extensions/DictionaryExtensions.cs | 29 +++ ...dLineParser.cs => SimpleSettingsParser.cs} | 31 ++- .../Settings/WireMockServerSettings.cs | 4 +- .../Settings/WireMockServerSettingsParser.cs | 23 +-- .../Extensions/DictionaryExtensionsTests.cs | 51 +++++ .../Settings/SimpleCommandLineParserTests.cs | 102 ---------- .../Settings/SimpleSettingsParserTests.cs | 177 ++++++++++++++++++ 9 files changed, 306 insertions(+), 122 deletions(-) create mode 100644 src/WireMock.Net/Extensions/DictionaryExtensions.cs rename src/WireMock.Net/Settings/{SimpleCommandLineParser.cs => SimpleSettingsParser.cs} (77%) create mode 100644 test/WireMock.Net.Tests/Extensions/DictionaryExtensionsTests.cs delete mode 100644 test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs create mode 100644 test/WireMock.Net.Tests/Settings/SimpleSettingsParserTests.cs diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs index 48f1577a..6781f733 100644 --- a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs @@ -29,7 +29,7 @@ static class Program XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); - if (!WireMockServerSettingsParser.TryParseArguments(args, out var settings, new WireMockLog4NetLogger())) + if (!WireMockServerSettingsParser.TryParseArguments(args, Environment.GetEnvironmentVariables(), out var settings, new WireMockLog4NetLogger())) { return; } diff --git a/src/WireMock.Net.StandAlone/StandAloneApp.cs b/src/WireMock.Net.StandAlone/StandAloneApp.cs index e2272978..98417683 100644 --- a/src/WireMock.Net.StandAlone/StandAloneApp.cs +++ b/src/WireMock.Net.StandAlone/StandAloneApp.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -24,7 +25,7 @@ public static class StandAloneApp [PublicAPI] public static WireMockServer Start(WireMockServerSettings settings) { - Guard.NotNull(settings, nameof(settings)); + Guard.NotNull(settings); var server = WireMockServer.Start(settings); @@ -42,7 +43,7 @@ public static class StandAloneApp [PublicAPI] public static WireMockServer Start(string[] args, IWireMockLogger? logger = null) { - Guard.NotNull(args, nameof(args)); + Guard.NotNull(args); if (TryStart(args, out var server, logger)) { @@ -61,9 +62,9 @@ public static class StandAloneApp [PublicAPI] public static bool TryStart(string[] args, [NotNullWhen(true)] out WireMockServer? server, IWireMockLogger? logger = null) { - Guard.NotNull(args, nameof(args)); + Guard.NotNull(args); - if (WireMockServerSettingsParser.TryParseArguments(args, out var settings, logger)) + if (WireMockServerSettingsParser.TryParseArguments(args, Environment.GetEnvironmentVariables(), out var settings, logger)) { settings.Logger?.Info("Version [{0}]", Version); settings.Logger?.Debug("Server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); diff --git a/src/WireMock.Net/Extensions/DictionaryExtensions.cs b/src/WireMock.Net/Extensions/DictionaryExtensions.cs new file mode 100644 index 00000000..600a8aa1 --- /dev/null +++ b/src/WireMock.Net/Extensions/DictionaryExtensions.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using Stef.Validation; + +namespace WireMock.Extensions; + +internal static class DictionaryExtensions +{ + public static bool TryGetStringValue(this IDictionary dictionary, string key, [NotNullWhen(true)] out string? value) + { + Guard.NotNull(dictionary); + + if (dictionary[key] is string valueIsString) + { + value = valueIsString; + return true; + } + + var valueToString = dictionary[key]?.ToString(); + if (valueToString != null) + { + value = valueToString; + return true; + } + + value = default; + return false; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Settings/SimpleCommandLineParser.cs b/src/WireMock.Net/Settings/SimpleSettingsParser.cs similarity index 77% rename from src/WireMock.Net/Settings/SimpleCommandLineParser.cs rename to src/WireMock.Net/Settings/SimpleSettingsParser.cs index 386e0883..91295e65 100644 --- a/src/WireMock.Net/Settings/SimpleCommandLineParser.cs +++ b/src/WireMock.Net/Settings/SimpleSettingsParser.cs @@ -1,17 +1,21 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using WireMock.Extensions; namespace WireMock.Settings; // Based on http://blog.gauffin.org/2014/12/simple-command-line-parser/ -internal class SimpleCommandLineParser +internal class SimpleSettingsParser { private const string Sigil = "--"; + private const string Prefix = $"{nameof(WireMockServerSettings)}__"; + private static readonly int PrefixLength = Prefix.Length; private IDictionary Arguments { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - public void Parse(string[] arguments) + public void Parse(string[] arguments, IDictionary? environment = null) { string currentName = string.Empty; @@ -44,6 +48,18 @@ internal class SimpleCommandLineParser { Arguments[currentName] = values.ToArray(); } + + // Now also parse environment + if (environment != null) + { + foreach (string key in environment.Keys) + { + if (key.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) && environment.TryGetStringValue(key, out var value)) + { + Arguments[key.Substring(PrefixLength)] = value.Split(' ').ToArray(); + } + } + } } public bool Contains(string name) @@ -85,7 +101,16 @@ internal class SimpleCommandLineParser return Contains(name); } - public int? GetIntValue(string name, int? defaultValue = null) + public int? GetIntValue(string name) + { + return GetValue(name, values => + { + var value = values.FirstOrDefault(); + return !string.IsNullOrEmpty(value) ? int.Parse(value) : null; + }, null); + } + + public int GetIntValue(string name, int defaultValue) { return GetValue(name, values => { diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net/Settings/WireMockServerSettings.cs index 8282a97d..3fb484ad 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettings.cs @@ -22,6 +22,8 @@ namespace WireMock.Settings; /// public class WireMockServerSettings { + internal const int DefaultStartTimeout = 10000; + /// /// Gets or sets the http port. /// @@ -81,7 +83,7 @@ public class WireMockServerSettings /// StartTimeout /// [PublicAPI] - public int StartTimeout { get; set; } = 10000; + public int StartTimeout { get; set; } = DefaultStartTimeout; /// /// Allow Partial Mapping (default set to false). diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index b8caea0e..d268cea9 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; @@ -18,16 +19,16 @@ public static class WireMockServerSettingsParser /// Parse commandline arguments into WireMockServerSettings. /// /// The commandline arguments + /// The environment settings (optional) /// The logger (optional, can be null) /// The parsed settings [PublicAPI] - public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings, - IWireMockLogger? logger = null) + public static bool TryParseArguments(string[] args, IDictionary? environment, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null) { Guard.HasNoNulls(args); - var parser = new SimpleCommandLineParser(); - parser.Parse(args); + var parser = new SimpleSettingsParser(); + parser.Parse(args, environment); if (parser.GetBoolSwitchValue("help")) { @@ -55,6 +56,7 @@ public static class WireMockServerSettingsParser RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"), SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true), + StartTimeout = parser.GetIntValue(nameof(WireMockServerSettings.StartTimeout), WireMockServerSettings.DefaultStartTimeout), ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"), UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), @@ -79,8 +81,7 @@ public static class WireMockServerSettingsParser return true; } - private static void ParseLoggerSettings(WireMockServerSettings settings, IWireMockLogger? logger, - SimpleCommandLineParser parser) + private static void ParseLoggerSettings(WireMockServerSettings settings, IWireMockLogger? logger, SimpleSettingsParser parser) { var loggerType = parser.GetStringValue("WireMockLogger"); switch (loggerType) @@ -103,7 +104,7 @@ public static class WireMockServerSettingsParser } } - private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SimpleCommandLineParser parser) + private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SimpleSettingsParser parser) { var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); if (!string.IsNullOrEmpty(proxyUrl)) @@ -135,7 +136,7 @@ public static class WireMockServerSettingsParser } } - private static void ParsePortSettings(WireMockServerSettings settings, SimpleCommandLineParser parser) + private static void ParsePortSettings(WireMockServerSettings settings, SimpleSettingsParser parser) { if (parser.Contains(nameof(WireMockServerSettings.Port))) { @@ -147,7 +148,7 @@ public static class WireMockServerSettingsParser } } - private static void ParseCertificateSettings(WireMockServerSettings settings, SimpleCommandLineParser parser) + private static void ParseCertificateSettings(WireMockServerSettings settings, SimpleSettingsParser parser) { var certificateSettings = new WireMockCertificateSettings { @@ -163,7 +164,7 @@ public static class WireMockServerSettingsParser } } - private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings, SimpleCommandLineParser parser) + private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) { string? proxyAddress = parser.GetStringValue("WebProxyAddress"); if (!string.IsNullOrEmpty(proxyAddress)) @@ -177,7 +178,7 @@ public static class WireMockServerSettingsParser } } - private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleCommandLineParser parser) + private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) { var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue"); var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue"); diff --git a/test/WireMock.Net.Tests/Extensions/DictionaryExtensionsTests.cs b/test/WireMock.Net.Tests/Extensions/DictionaryExtensionsTests.cs new file mode 100644 index 00000000..55934f85 --- /dev/null +++ b/test/WireMock.Net.Tests/Extensions/DictionaryExtensionsTests.cs @@ -0,0 +1,51 @@ +using System.Collections; +using FluentAssertions; +using WireMock.Extensions; +using Xunit; + +namespace WireMock.Net.Tests.Extensions; + +public class DictionaryExtensionsTests +{ + [Fact] + public void TryGetStringValue_WhenKeyExistsAndValueIsString_ReturnsTrueAndStringValue() + { + // Arrange + var dictionary = new Hashtable { { "key", "value" } }; + + // Act + var result = dictionary.TryGetStringValue("key", out var value); + + // Assert + result.Should().BeTrue(); + value.Should().Be("value"); + } + + [Fact] + public void TryGetStringValue_WhenKeyExistsAndValueIsNotString_ReturnsTrueAndStringValue() + { + // Arrange + var dictionary = new Hashtable { { "key", 123 } }; + + // Act + var result = dictionary.TryGetStringValue("key", out var value); + + // Assert + result.Should().BeTrue(); + value.Should().Be("123"); + } + + [Fact] + public void TryGetStringValue_WhenKeyDoesNotExist_ReturnsFalseAndNull() + { + // Arrange + var dictionary = new Hashtable { { "otherKey", "value" } }; + + // Act + var result = dictionary.TryGetStringValue("key", out var value); + + // Assert + result.Should().BeFalse(); + value.Should().BeNull(); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs b/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs deleted file mode 100644 index b6c1d686..00000000 --- a/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -using NFluent; -using WireMock.Settings; -using Xunit; - -namespace WireMock.Net.Tests.Settings; - -public class SimpleCommandLineParserTests -{ - private readonly SimpleCommandLineParser _parser; - - public SimpleCommandLineParserTests() - { - _parser = new SimpleCommandLineParser(); - } - - [Fact] - public void SimpleCommandLineParser_Parse_Arguments() - { - // Assign - _parser.Parse(new[] { "--test1", "one", "--test2", "two", "--test3", "three" }); - - // Act - string? value1 = _parser.GetStringValue("test1"); - string? value2 = _parser.GetStringValue("test2"); - string? value3 = _parser.GetStringValue("test3"); - - // Assert - Check.That(value1).IsEqualTo("one"); - Check.That(value2).IsEqualTo("two"); - Check.That(value3).IsEqualTo("three"); - } - - [Fact] - public void SimpleCommandLineParser_Parse_ArgumentsAsCombinedKeyAndValue() - { - // Assign - _parser.Parse(new[] { "--test1 one", "--test2 two", "--test3 three" }); - - // Act - string? value1 = _parser.GetStringValue("test1"); - string? value2 = _parser.GetStringValue("test2"); - string? value3 = _parser.GetStringValue("test3"); - - // Assert - Check.That(value1).IsEqualTo("one"); - Check.That(value2).IsEqualTo("two"); - Check.That(value3).IsEqualTo("three"); - } - - [Fact] - public void SimpleCommandLineParser_Parse_ArgumentsMixed() - { - // Assign - _parser.Parse(new[] { "--test1 one", "--test2", "two", "--test3 three" }); - - // Act - string? value1 = _parser.GetStringValue("test1"); - string? value2 = _parser.GetStringValue("test2"); - string? value3 = _parser.GetStringValue("test3"); - - // Assert - Check.That(value1).IsEqualTo("one"); - Check.That(value2).IsEqualTo("two"); - Check.That(value3).IsEqualTo("three"); - } - - [Fact] - public void SimpleCommandLineParser_Parse_GetBoolValue() - { - // Assign - _parser.Parse(new[] { "'--test1", "false'", "--test2 true" }); - - // Act - bool value1 = _parser.GetBoolValue("test1"); - bool value2 = _parser.GetBoolValue("test2"); - bool value3 = _parser.GetBoolValue("test3", true); - - // Assert - Check.That(value1).IsEqualTo(false); - Check.That(value2).IsEqualTo(true); - Check.That(value3).IsEqualTo(true); - } - - [Fact] - public void SimpleCommandLineParser_Parse_GetIntValue() - { - // Assign - _parser.Parse(new[] { "--test1", "42", "--test2 55" }); - - // Act - int? value1 = _parser.GetIntValue("test1"); - int? value2 = _parser.GetIntValue("test2"); - int? value3 = _parser.GetIntValue("test3", 100); - int? value4 = _parser.GetIntValue("test4"); - - // Assert - Check.That(value1).IsEqualTo(42); - Check.That(value2).IsEqualTo(55); - Check.That(value3).IsEqualTo(100); - Check.That(value4).IsNull(); - } -} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Settings/SimpleSettingsParserTests.cs b/test/WireMock.Net.Tests/Settings/SimpleSettingsParserTests.cs new file mode 100644 index 00000000..d38acee0 --- /dev/null +++ b/test/WireMock.Net.Tests/Settings/SimpleSettingsParserTests.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using FluentAssertions; +using NFluent; +using WireMock.Settings; +using WireMock.Types; +using Xunit; + +namespace WireMock.Net.Tests.Settings; + +public class SimpleSettingsParserTests +{ + private readonly SimpleSettingsParser _parser; + + public SimpleSettingsParserTests() + { + _parser = new SimpleSettingsParser(); + } + + [Fact] + public void SimpleCommandLineParser_Parse_Arguments() + { + // Assign + _parser.Parse(new[] { "--test1", "one", "--test2", "2", "--test3", "3", "--test4", "true", "--test5", "Https" }); + + // Act + string? stringValue = _parser.GetStringValue("test1"); + int? intOptional = _parser.GetIntValue("test2"); + int intWithDefault = _parser.GetIntValue("test3", 42); + bool? boolWithDefault = _parser.GetBoolValue("test4"); + HostingScheme? enumOptional = _parser.GetEnumValue("test5"); + HostingScheme enumWithDefault = _parser.GetEnumValue("test99", HostingScheme.HttpAndHttps); + + // Assert + stringValue.Should().Be("one"); + intOptional.Should().Be(2); + intWithDefault.Should().Be(3); + boolWithDefault.Should().Be(true); + enumOptional.Should().Be(HostingScheme.Https); + enumWithDefault.Should().Be(HostingScheme.HttpAndHttps); + } + + [Fact] + public void SimpleCommandLineParser_Parse_Environment() + { + // Assign + var env = new Dictionary + { + { "WireMockServerSettings__test1", "one" }, + { "WireMockServerSettings__test2", "two" } + }; + _parser.Parse(new string[0], env); + + // Act + string? value1 = _parser.GetStringValue("test1"); + string? value2 = _parser.GetStringValue("test2"); + + // Assert + Check.That(value1).IsEqualTo("one"); + Check.That(value2).IsEqualTo("two"); + } + + [Fact] + public void SimpleCommandLineParser_Parse_ArgumentsAsCombinedKeyAndValue() + { + // Assign + _parser.Parse(new[] { "--test1 one", "--test2 two", "--test3 three" }); + + // Act + string? value1 = _parser.GetStringValue("test1"); + string? value2 = _parser.GetStringValue("test2"); + string? value3 = _parser.GetStringValue("test3"); + + // Assert + Check.That(value1).IsEqualTo("one"); + Check.That(value2).IsEqualTo("two"); + Check.That(value3).IsEqualTo("three"); + } + + [Fact] + public void SimpleCommandLineParser_Parse_ArgumentsMixed() + { + // Assign + _parser.Parse(new[] { "--test1 one", "--test2", "two", "--test3 three" }); + + // Act + string? value1 = _parser.GetStringValue("test1"); + string? value2 = _parser.GetStringValue("test2"); + string? value3 = _parser.GetStringValue("test3"); + + // Assert + Check.That(value1).IsEqualTo("one"); + Check.That(value2).IsEqualTo("two"); + Check.That(value3).IsEqualTo("three"); + } + + [Fact] + public void SimpleCommandLineParser_Parse_GetBoolValue() + { + // Assign + _parser.Parse(new[] { "'--test1", "false'", "--test2 true" }); + + // Act + bool value1 = _parser.GetBoolValue("test1"); + bool value2 = _parser.GetBoolValue("test2"); + bool value3 = _parser.GetBoolValue("test3", true); + + // Assert + Check.That(value1).IsEqualTo(false); + Check.That(value2).IsEqualTo(true); + Check.That(value3).IsEqualTo(true); + } + + [Fact] + public void SimpleCommandLineParser_Parse_Environment_GetBoolValue() + { + // Assign + var env = new Dictionary + { + { "WireMockServerSettings__test1", "false" }, + { "WireMockServerSettings__test2", "true" } + }; + _parser.Parse(new string[0], env); + + // Act + bool value1 = _parser.GetBoolValue("test1"); + bool value2 = _parser.GetBoolValue("test2"); + bool value3 = _parser.GetBoolValue("test3", true); + + // Assert + Check.That(value1).IsEqualTo(false); + Check.That(value2).IsEqualTo(true); + Check.That(value3).IsEqualTo(true); + } + + [Fact] + public void SimpleCommandLineParser_Parse_GetIntValue() + { + // Assign + _parser.Parse(new[] { "--test1", "42", "--test2 55" }); + + // Act + int? value1 = _parser.GetIntValue("test1"); + int? value2 = _parser.GetIntValue("test2"); + int? value3 = _parser.GetIntValue("test3", 100); + int? value4 = _parser.GetIntValue("test4"); + + // Assert + Check.That(value1).IsEqualTo(42); + Check.That(value2).IsEqualTo(55); + Check.That(value3).IsEqualTo(100); + Check.That(value4).IsNull(); + } + + [Fact] + public void SimpleCommandLineParser_Parse_Environment_GetIntValue() + { + // Assign + var env = new Dictionary + { + { "WireMockServerSettings__test1", "42" }, + { "WireMockServerSETTINGS__TEST2", "55" } + }; + _parser.Parse(new string[0], env); + + // Act + int? value1 = _parser.GetIntValue("test1"); + int? value2 = _parser.GetIntValue("test2"); + int? value3 = _parser.GetIntValue("test3", 100); + int? value4 = _parser.GetIntValue("test4"); + + // Assert + Check.That(value1).IsEqualTo(42); + Check.That(value2).IsEqualTo(55); + Check.That(value3).IsEqualTo(100); + Check.That(value4).IsNull(); + } +} \ No newline at end of file