From fdc433f0ce6b3e11e09f40ff60b71975bd7f2470 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 15 Jun 2019 10:22:28 +0200 Subject: [PATCH] QueryStringParser --- Directory.Build.props | 2 +- GitHubReleaseNotes.txt | 2 +- .../MainApp.cs | 9 + src/WireMock.Net/RequestBuilders/Request.cs | 1 - src/WireMock.Net/RequestMessage.cs | 35 +-- src/WireMock.Net/Util/QueryStringParser.cs | 41 ++++ .../Util/QueryStringParserTests.cs | 210 ++++++++++++++++++ .../WireMock.Net.Tests.csproj | 2 +- 8 files changed, 264 insertions(+), 38 deletions(-) create mode 100644 src/WireMock.Net/Util/QueryStringParser.cs create mode 100644 test/WireMock.Net.Tests/Util/QueryStringParserTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 2b63653b..9937770d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.18 + 1.0.19 diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index 860f0b5a..5dd7de2e 100644 --- a/GitHubReleaseNotes.txt +++ b/GitHubReleaseNotes.txt @@ -1,3 +1,3 @@ https://github.com/StefH/GitHubReleaseNotes -GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.18.0 \ No newline at end of file +GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.19.0 \ No newline at end of file diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 4c3ef47b..cb3d0a39 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -438,6 +438,15 @@ namespace WireMock.Net.ConsoleApplication .WithBody("ok") ); + server.Given(Request.Create() + .WithPath("/services/query/") + .WithParam("q", "SELECT Id from User where username='user@gmail.com'") + .UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBodyAsJson(new { Id = "5bdf076c-5654-4b3e-842c-7caf1fabf8c9" })); + System.Console.WriteLine("Press any key to stop the server"); System.Console.ReadKey(); server.Stop(); diff --git a/src/WireMock.Net/RequestBuilders/Request.cs b/src/WireMock.Net/RequestBuilders/Request.cs index 054f8d2d..3fa1b9f9 100644 --- a/src/WireMock.Net/RequestBuilders/Request.cs +++ b/src/WireMock.Net/RequestBuilders/Request.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Linq; using WireMock.Matchers; using WireMock.Matchers.Request; -using WireMock.Util; using WireMock.Validation; namespace WireMock.RequestBuilders diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 46583b43..f89da6a5 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -171,40 +171,7 @@ namespace WireMock Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); Cookies = cookies; RawQuery = WebUtility.UrlDecode(urlDetails.Url.Query); - Query = ParseQuery(RawQuery); - } - - private static IDictionary> ParseQuery(string queryString) - { - if (string.IsNullOrEmpty(queryString)) - { - return null; - } - - if (queryString.StartsWith("?")) - { - queryString = queryString.Substring(1); - } - - return queryString.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) - .Aggregate(new Dictionary>(), - (dict, term) => - { - string[] parts = term.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); - string key = parts[0]; - if (!dict.ContainsKey(key)) - { - dict.Add(key, new WireMockList()); - } - - if (parts.Length == 2) - { - string[] values = parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); - dict[key].AddRange(values); - } - - return dict; - }); + Query = QueryStringParser.Parse(RawQuery); } /// diff --git a/src/WireMock.Net/Util/QueryStringParser.cs b/src/WireMock.Net/Util/QueryStringParser.cs new file mode 100644 index 00000000..a512a1b1 --- /dev/null +++ b/src/WireMock.Net/Util/QueryStringParser.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WireMock.Util +{ + /// + /// Based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net + /// + internal static class QueryStringParser + { + public static IDictionary> Parse(string queryString) + { + if (string.IsNullOrEmpty(queryString)) + { + return new Dictionary>(); + } + + string[] JoinParts(string[] parts) + { + if (parts.Length > 2) + { + return new[] { string.Join("=", parts, 1, parts.Length - 1) }; + } + + if (parts.Length > 1) + { + return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2" + } + + return new string[0]; + } + + return queryString.TrimStart('?') + .Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue" + .Select(parameter => parameter.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries)) + .GroupBy(parts => parts[0], JoinParts) + .ToDictionary(grouping => grouping.Key, grouping => new WireMockList(grouping.SelectMany(x => x))); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs b/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs new file mode 100644 index 00000000..799c2940 --- /dev/null +++ b/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs @@ -0,0 +1,210 @@ +using FluentAssertions; +using System.Collections.Generic; +using WireMock.Util; +using Xunit; + +namespace WireMock.Net.Tests.Util +{ + public class QueryStringParserTests + { + [Fact] + public void Parse_WithNullString() + { + // Assign + string query = null; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Should().Equal(new Dictionary>()); + } + + [Fact] + public void Parse_WithEmptyString() + { + // Assign + string query = ""; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Should().Equal(new Dictionary>()); + } + + [Fact] + public void Parse_WithQuestionMark() + { + // Assign + string query = "?"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Should().Equal(new Dictionary>()); + } + + [Fact] + public void Parse_With1Param() + { + // Assign + string query = "?key=bla/blub.xml"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("bla/blub.xml")); + } + + [Fact] + public void Parse_With2Params() + { + // Assign + string query = "?x=1&y=2"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(2); + result["x"].Should().Equal(new WireMockList("1")); + result["y"].Should().Equal(new WireMockList("2")); + } + + [Fact] + public void Parse_With1ParamNoValue() + { + // Assign + string query = "?empty"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["empty"].Should().Equal(new WireMockList()); + } + + [Fact] + public void Parse_With1ParamNoValueWithEqualSign() + { + // Assign + string query = "?empty="; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["empty"].Should().Equal(new WireMockList()); + } + + [Fact] + public void Parse_With1ParamAndJustAndSign() + { + // Assign + string query = "?key=1&"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("1")); + } + + [Fact] + public void Parse_With2ParamsAndWhereOneHasAQuestion() + { + // Assign + string query = "?key=value?&b=c"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(2); + result["key"].Should().Equal(new WireMockList("value?")); + result["b"].Should().Equal(new WireMockList("c")); + } + + [Fact] + public void Parse_With1ParamWithEqualSign() + { + // Assign + string query = "?key=value=what"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("value=what")); + } + + [Fact] + public void Parse_WithMultipleParamWithSameKeySeparatedBySemiColon() + { + // Assign + string query = "?key=value;key=anotherValue"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList(new[] { "value", "anotherValue" })); + } + + [Fact] + public void Parse_With1ParamContainingComma() + { + // Assign + string query = "?key=1,2&key=3"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList(new[] { "1", "2", "3" })); + } + + [Fact] + public void Parse_WithMultipleParamWithSameKey() + { + // Assign + string query = "?key=value&key=anotherValue"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList(new[] { "value", "anotherValue" })); + } + + [Fact] + public void Parse_WithComplex() + { + // Assign + string query = "?q=energy+edge&rls=com.microsoft:en-au&ie=UTF-8&oe=UTF-8&startIndex=&startPage=1%22"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(6); + result["q"].Should().Equal(new WireMockList("energy+edge")); + result["rls"].Should().Equal(new WireMockList("com.microsoft:en-au")); + result["ie"].Should().Equal(new WireMockList("UTF-8")); + result["oe"].Should().Equal(new WireMockList("UTF-8")); + result["startIndex"].Should().Equal(new WireMockList()); + result["startPage"].Should().Equal(new WireMockList("1%22")); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index 6936f5e1..26b90ea4 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -34,8 +34,8 @@ all runtime; build; native; contentfiles; analyzers + -