diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs index 61e70fb8..023ddebc 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs @@ -22,7 +22,7 @@ public class MatcherModel public object? Pattern { get; set; } /// - /// Gets or sets the patterns. Can be array of strings (default) or an array of objects. + /// Gets or sets the patterns. Can be an array of strings (default) or an array of objects. /// public object[]? Patterns { get; set; } diff --git a/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs b/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs index 0350782a..434a1989 100644 --- a/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs +++ b/src/WireMock.Net.Abstractions/BuilderExtensions/RequestModelBuilder.cs @@ -1,6 +1,7 @@ // Copyright © WireMock.Net using System; +using WireMock.Validators; // ReSharper disable once CheckNamespace namespace WireMock.Admin.Mappings; @@ -94,9 +95,14 @@ public partial class RequestModelBuilder } /// - /// Set the Path. + /// Set the Path. Must start with a forward slash (/). /// - public RequestModelBuilder WithPath(string value) => WithPath(() => value); + public RequestModelBuilder WithPath(string value) + { + PathValidator.ValidateAndThrow(value); + + return WithPath(() => value); + } /// /// Set the Path. diff --git a/src/WireMock.Net.Abstractions/Validators/PathValidator.cs b/src/WireMock.Net.Abstractions/Validators/PathValidator.cs new file mode 100644 index 00000000..9763f6a2 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Validators/PathValidator.cs @@ -0,0 +1,19 @@ +// Copyright © WireMock.Net + +using System; + +namespace WireMock.Validators; + +public static class PathValidator +{ + /// + /// A valid path must start with a '/' and cannot be null, empty or whitespace. + /// + public static void ValidateAndThrow(string? path, string? paramName = null) + { + if (string.IsNullOrWhiteSpace(path) || path?.StartsWith("/") == false) + { + throw new ArgumentException("Path must start with a '/' and cannot be null, empty or whitespace.", paramName ?? nameof(path)); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs index 2a5939bb..7ff11fd8 100644 --- a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs +++ b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs @@ -4,6 +4,7 @@ using System; using Stef.Validation; using WireMock.Matchers; using WireMock.Matchers.Request; +using WireMock.Validators; namespace WireMock.RequestBuilders; @@ -34,6 +35,10 @@ public partial class Request public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths) { Guard.NotNullOrEmpty(paths); + foreach (var path in paths) + { + PathValidator.ValidateAndThrow(path, nameof(paths)); + } _requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths)); return this; diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs index 57a1510b..14cb09b3 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode.cs @@ -109,7 +109,7 @@ public partial class MappingConverterTests var guid = new Guid("8e7b9ab7-e18e-4502-8bc9-11e6679811cc"); var request = Request.Create() .UsingGet() - .WithPath("test_path") + .WithPath("/test_path") .WithParam("q", "42") .WithClientIP("112.123.100.99") .WithHeader("h-key", "h-value") diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt index def84b6f..5a4b9e1b 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsFalse.verified.txt @@ -1,7 +1,7 @@ builder .Given(Request.Create() .UsingMethod("GET") - .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or)) + .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or)) .WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42")) .WithClientIP("112.123.100.99") .WithHeader("h-key", "h-value", true) diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt index b7dc2b58..b85622a1 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Builder_And_AddStartIsTrue.verified.txt @@ -2,7 +2,7 @@ builder .Given(Request.Create() .UsingMethod("GET") - .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or)) + .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or)) .WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42")) .WithClientIP("112.123.100.99") .WithHeader("h-key", "h-value", true) diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt index 93304d65..80754949 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsFalse.verified.txt @@ -1,7 +1,7 @@ server .Given(Request.Create() .UsingMethod("GET") - .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or)) + .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or)) .WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42")) .WithClientIP("112.123.100.99") .WithHeader("h-key", "h-value", true) diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt index cb224cd2..a2589a27 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt +++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.ToCSharpCode_With_Server_And_AddStartIsTrue.verified.txt @@ -2,7 +2,7 @@ server .Given(Request.Create() .UsingMethod("GET") - .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "test_path", false, WireMock.Matchers.MatchOperator.Or)) + .WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/test_path", false, WireMock.Matchers.MatchOperator.Or)) .WithParam("q", new ExactMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, false, WireMock.Matchers.MatchOperator.And, "42")) .WithClientIP("112.123.100.99") .WithHeader("h-key", "h-value", true) diff --git a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt index 1e023f54..cda79f01 100644 --- a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt +++ b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.ToMapping_UseDefinedRequestMatchers_True.verified.txt @@ -9,7 +9,7 @@ Matchers: [ { Name: WildcardMatcher, - Pattern: x, + Pattern: /x, IgnoreCase: false } ] diff --git a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs index dad9fe67..c8126cee 100644 --- a/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs +++ b/test/WireMock.Net.Tests/Serialization/ProxyMappingConverterTests.cs @@ -56,7 +56,7 @@ public class ProxyMappingConverterTests var request = Request.Create() .UsingPost() - .WithPath("x") + .WithPath("/x") .WithParam("p1", "p1-v") .WithParam("p2", "p2-v") .WithHeader("Content-Type", new ContentTypeMatcher("text/plain")) diff --git a/test/WireMock.Net.Tests/Validators/PathValidatorTests.cs b/test/WireMock.Net.Tests/Validators/PathValidatorTests.cs new file mode 100644 index 00000000..136f7c34 --- /dev/null +++ b/test/WireMock.Net.Tests/Validators/PathValidatorTests.cs @@ -0,0 +1,42 @@ +// Copyright © WireMock.Net + +using System; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using WireMock.Validators; +using Xunit; + +namespace WireMock.Net.Tests.Validators; + +[ExcludeFromCodeCoverage] +public class PathValidatorTests +{ + [Fact] + public void ValidateAndThrow_ValidPath_DoesNotThrow() + { + Action act = () => PathValidator.ValidateAndThrow("/valid/path"); + act.Should().NotThrow(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\r")] + [InlineData("\n")] + [InlineData("\t")] + public void ValidateAndThrow_InvalidPath_ThrowsArgumentException_WithDefaultParamName(string? path) + { + Action act = () => PathValidator.ValidateAndThrow(path); + var ex = act.Should().Throw().Which; + ex.Message.Should().StartWith("Path must start with a '/' and cannot be null, empty or whitespace."); + ex.ParamName.Should().Be("path"); + } + + [Fact] + public void ValidateAndThrow_NoLeadingSlash_ThrowsArgumentException_WithProvidedParamName() + { + Action act = () => PathValidator.ValidateAndThrow("noSlash", "myParam"); + var ex = act.Should().Throw().Which; + ex.ParamName.Should().Be("myParam"); + } +} \ No newline at end of file